use std::fmt;
use std::hash::{Hash, Hasher};
impl Hash for Sexp {
fn hash<H: Hasher>(&self, h: &mut H) {
match self {
Self::Nil => 0u8.hash(h),
Self::Atom(a) => {
1u8.hash(h);
a.hash(h);
}
Self::List(items) => {
2u8.hash(h);
items.len().hash(h);
for i in items {
i.hash(h);
}
}
Self::Quote(inner) => {
3u8.hash(h);
inner.hash(h);
}
Self::Quasiquote(inner) => {
4u8.hash(h);
inner.hash(h);
}
Self::Unquote(inner) => {
5u8.hash(h);
inner.hash(h);
}
Self::UnquoteSplice(inner) => {
6u8.hash(h);
inner.hash(h);
}
}
}
}
impl Hash for Atom {
fn hash<H: Hasher>(&self, h: &mut H) {
match self {
Self::Symbol(s) => {
0u8.hash(h);
s.hash(h);
}
Self::Keyword(s) => {
1u8.hash(h);
s.hash(h);
}
Self::Str(s) => {
2u8.hash(h);
s.hash(h);
}
Self::Int(n) => {
3u8.hash(h);
n.hash(h);
}
Self::Float(f) => {
4u8.hash(h);
f.to_bits().hash(h);
}
Self::Bool(b) => {
5u8.hash(h);
b.hash(h);
}
}
}
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Sexp {
Nil,
Atom(Atom),
List(Vec<Sexp>),
Quote(Box<Sexp>),
Quasiquote(Box<Sexp>),
Unquote(Box<Sexp>),
UnquoteSplice(Box<Sexp>),
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Atom {
Symbol(String),
Keyword(String),
Str(String),
Int(i64),
Float(f64),
Bool(bool),
}
impl Sexp {
pub fn symbol(s: impl Into<String>) -> Self {
Self::Atom(Atom::Symbol(s.into()))
}
pub fn keyword(s: impl Into<String>) -> Self {
Self::Atom(Atom::Keyword(s.into()))
}
pub fn string(s: impl Into<String>) -> Self {
Self::Atom(Atom::Str(s.into()))
}
pub fn int(n: i64) -> Self {
Self::Atom(Atom::Int(n))
}
pub fn float(n: f64) -> Self {
Self::Atom(Atom::Float(n))
}
pub fn boolean(b: bool) -> Self {
Self::Atom(Atom::Bool(b))
}
pub fn is_list(&self) -> bool {
matches!(self, Self::List(_))
}
pub fn as_list(&self) -> Option<&[Sexp]> {
match self {
Self::List(xs) => Some(xs),
_ => None,
}
}
pub fn as_symbol(&self) -> Option<&str> {
match self {
Self::Atom(Atom::Symbol(s)) => Some(s),
_ => None,
}
}
pub fn as_keyword(&self) -> Option<&str> {
match self {
Self::Atom(Atom::Keyword(s)) => Some(s),
_ => None,
}
}
pub fn as_string(&self) -> Option<&str> {
match self {
Self::Atom(Atom::Str(s)) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Self::Atom(Atom::Int(n)) => Some(*n),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Self::Atom(Atom::Float(n)) => Some(*n),
Self::Atom(Atom::Int(n)) => Some(*n as f64),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
Self::Atom(Atom::Bool(b)) => Some(*b),
_ => None,
}
}
pub fn as_symbol_or_string(&self) -> Option<&str> {
self.as_symbol().or_else(|| self.as_string())
}
}
impl fmt::Display for Sexp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Nil => f.write_str("()"),
Self::Atom(a) => match a {
Atom::Symbol(s) => f.write_str(s),
Atom::Keyword(s) => write!(f, ":{s}"),
Atom::Str(s) => write!(f, "{s:?}"),
Atom::Int(n) => write!(f, "{n}"),
Atom::Float(n) => write!(f, "{n}"),
Atom::Bool(true) => f.write_str("#t"),
Atom::Bool(false) => f.write_str("#f"),
},
Self::List(xs) => {
f.write_str("(")?;
for (i, x) in xs.iter().enumerate() {
if i > 0 {
f.write_str(" ")?;
}
write!(f, "{x}")?;
}
f.write_str(")")
}
Self::Quote(inner) => write!(f, "'{inner}"),
Self::Quasiquote(inner) => write!(f, "`{inner}"),
Self::Unquote(inner) => write!(f, ",{inner}"),
Self::UnquoteSplice(inner) => write!(f, ",@{inner}"),
}
}
}