use std::sync::Arc;
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum Literal {
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Bytes(Vec<u8>),
Null,
Record(Vec<(Arc<str>, Self)>),
List(Vec<Self>),
Closure {
param: Arc<str>,
body: Box<crate::Expr>,
env: Vec<(Arc<str>, Self)>,
},
}
impl Literal {
#[must_use]
pub const fn type_name(&self) -> &'static str {
match self {
Self::Bool(_) => "bool",
Self::Int(_) => "int",
Self::Float(_) => "float",
Self::Str(_) => "string",
Self::Bytes(_) => "bytes",
Self::Null => "null",
Self::Record(_) => "record",
Self::List(_) => "list",
Self::Closure { .. } => "function",
}
}
#[must_use]
pub const fn is_null(&self) -> bool {
matches!(self, Self::Null)
}
#[must_use]
pub const fn as_bool(&self) -> Option<bool> {
match self {
Self::Bool(b) => Some(*b),
_ => None,
}
}
#[must_use]
pub const fn as_int(&self) -> Option<i64> {
match self {
Self::Int(n) => Some(*n),
_ => None,
}
}
#[must_use]
pub const fn as_float(&self) -> Option<f64> {
match self {
Self::Float(f) => Some(*f),
_ => None,
}
}
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::Str(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_record(&self) -> Option<&[(Arc<str>, Self)]> {
match self {
Self::Record(fields) => Some(fields),
_ => None,
}
}
#[must_use]
pub fn as_list(&self) -> Option<&[Self]> {
match self {
Self::List(items) => Some(items),
_ => None,
}
}
#[must_use]
pub fn field(&self, name: &str) -> Option<&Self> {
match self {
Self::Record(fields) => fields.iter().find(|(k, _)| &**k == name).map(|(_, v)| v),
_ => None,
}
}
}
impl PartialEq for Literal {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Bool(a), Self::Bool(b)) => a == b,
(Self::Int(a), Self::Int(b)) => a == b,
(Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
(Self::Str(a), Self::Str(b)) => a == b,
(Self::Bytes(a), Self::Bytes(b)) => a == b,
(Self::Null, Self::Null) => true,
(Self::Record(a), Self::Record(b)) => a == b,
(Self::List(a), Self::List(b)) => a == b,
(
Self::Closure {
param: p1,
body: b1,
env: e1,
},
Self::Closure {
param: p2,
body: b2,
env: e2,
},
) => p1 == p2 && b1 == b2 && e1 == e2,
_ => false,
}
}
}
impl Eq for Literal {}
impl std::hash::Hash for Literal {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Self::Bool(b) => b.hash(state),
Self::Int(n) => n.hash(state),
Self::Float(f) => f.to_bits().hash(state),
Self::Str(s) => s.hash(state),
Self::Bytes(b) => b.hash(state),
Self::Null => {}
Self::Record(fields) => fields.hash(state),
Self::List(items) => items.hash(state),
Self::Closure { param, body, env } => {
param.hash(state);
body.hash(state);
env.hash(state);
}
}
}
}
impl std::fmt::Display for Literal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bool(b) => write!(f, "{b}"),
Self::Int(n) => write!(f, "{n}"),
Self::Float(v) => write!(f, "{v}"),
Self::Str(s) => write!(f, "\"{s}\""),
Self::Bytes(b) => write!(f, "<{} bytes>", b.len()),
Self::Null => write!(f, "null"),
Self::Record(fields) => {
write!(f, "{{ ")?;
for (i, (k, v)) in fields.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{k}: {v}")?;
}
write!(f, " }}")
}
Self::List(items) => {
write!(f, "[")?;
for (i, v) in items.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{v}")?;
}
write!(f, "]")
}
Self::Closure { param, .. } => write!(f, "<closure λ{param}>"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn float_equality_uses_bits() {
let a = Literal::Float(f64::NAN);
let b = Literal::Float(f64::NAN);
assert_eq!(a, b);
}
#[test]
fn type_names() {
assert_eq!(Literal::Bool(true).type_name(), "bool");
assert_eq!(Literal::Int(42).type_name(), "int");
assert_eq!(Literal::Null.type_name(), "null");
assert_eq!(Literal::Record(vec![]).type_name(), "record");
assert_eq!(Literal::List(vec![]).type_name(), "list");
}
#[test]
fn record_field_lookup() {
let rec = Literal::Record(vec![
(Arc::from("name"), Literal::Str("alice".into())),
(Arc::from("age"), Literal::Int(30)),
]);
assert_eq!(rec.field("name"), Some(&Literal::Str("alice".into())));
assert_eq!(rec.field("age"), Some(&Literal::Int(30)));
assert_eq!(rec.field("missing"), None);
}
}