diff options
author | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-07-30 17:48:11 +0300 |
---|---|---|
committer | Joel Kronqvist <joel.kronqvist@iki.fi> | 2025-07-30 17:48:11 +0300 |
commit | a4b09d523e0b500de29c08b32abc13af8e509299 (patch) | |
tree | 77e0bdfa5b39c0789e829e698846fb06becbf7e1 | |
parent | d2d4d5cffdce0ce28e59732f56a771f8fc3431de (diff) | |
parent | cd68a2880db1400ae09ce0df64994b2bae33a3c1 (diff) | |
download | myslip-a4b09d523e0b500de29c08b32abc13af8e509299.tar.gz myslip-a4b09d523e0b500de29c08b32abc13af8e509299.zip |
Merge work done on laptop to desktop computer
Merge branch 'devel' of ssh://cron4.fi/~/melisp into devel
-rw-r--r-- | src/sexp/mod.rs | 20 | ||||
-rw-r--r-- | src/sexp/step.rs | 172 | ||||
-rw-r--r-- | src/sexp/util.rs | 29 |
3 files changed, 183 insertions, 38 deletions
diff --git a/src/sexp/mod.rs b/src/sexp/mod.rs index 020ba63..f6c50d7 100644 --- a/src/sexp/mod.rs +++ b/src/sexp/mod.rs @@ -32,3 +32,23 @@ pub enum SExp { SCons(Box<SExp>, Box<SExp>), Atom(SLeaf), } + +use SExp::*; +use SLeaf::*; + +impl SExp { + pub fn is_value(&self) -> bool { + match self { + SCons(a, _) => **a == Atom(Quote), + Atom(Var(_)) => false, + Atom(_) => true, + } + } + + pub fn consists_of_values(&self) -> bool { + self.is_value() || match self { + SCons(a, b) if (*a).is_value() => b.consists_of_values(), + _ => false + } + } +} diff --git a/src/sexp/step.rs b/src/sexp/step.rs index a6ce664..0989ee3 100644 --- a/src/sexp/step.rs +++ b/src/sexp/step.rs @@ -13,23 +13,23 @@ impl SExp { /// use melisp::sexp::{SExp::*, SLeaf::*, util::*}; /// /// assert_eq!( - /// scons(Add, scons(1, 1)).step().unwrap(), - /// Atom(Int(2)) + /// scons(Add, scons(1, scons(1, Nil))).step(), + /// Ok(Atom(Int(2))) /// ); /// /// assert_eq!( - /// scons(Sub, scons(1, 2)).step().unwrap(), - /// Atom(Int(-1)) + /// scons(Sub, scons(1, scons(2, Nil))).step(), + /// Ok(Atom(Int(-1))) /// ); /// /// assert_eq!( - /// scons(Mul, scons(2, 3)).step().unwrap(), - /// Atom(Int(6)) + /// scons(Mul, scons(2, scons(3, Nil))).step(), + /// Ok(Atom(Int(6))) /// ); /// /// assert_eq!( - /// scons(Div, scons(6, 2)).step().unwrap(), - /// Atom(Int(3)) + /// scons(Div, scons(6, scons(2, Nil))).step(), + /// Ok(Atom(Int(3))) /// ); /// ``` /// @@ -38,57 +38,153 @@ impl SExp { /// use melisp::sexp::{SExp::*, SLeaf::*, util::*}; /// /// assert_eq!( - /// scons(Div, scons(5, 2)).step().unwrap(), - /// Atom(Int(2)) + /// scons(Div, scons(5, scons(2, Nil))).step(), + /// Ok(Atom(Int(2))) + /// ); + /// ``` + /// + /// Also, addition and multiplication can take more than two arguments: + /// ```rust + /// use melisp::sexp::{SExp::*, SLeaf::*, util::*}; + /// + /// assert_eq!( + /// scons(Add, scons(1, scons(2, scons(3, Nil)))).step(), + /// Ok(Atom(Int(6))) /// ); /// + /// assert_eq!( + /// scons(Mul, scons(1, scons(2, scons(3, Nil)))).step(), + /// Ok(Atom(Int(6))) + /// ); /// ``` /// - /// Here's an example of a bit more complicated expression: + /// Here's an example of a bit more complicated expression + /// from wikipedias article on s-expressions. /// ```rust /// use melisp::sexp::{SExp::*, SLeaf::*, util::*}; /// /// fn main() { - /// let exp = scons(Mul, scons( - /// 2, - /// scons(Add, scons( - /// scons(Div, scons(5, 2)), - /// 4 - /// )) - /// )); - /// - /// let exp = exp.step().unwrap(); - /// assert_eq!(exp, scons(Mul, scons(2, scons(Add, scons(2, 4))))); + /// let exp = scons( + /// Mul, + /// scons( + /// 2, + /// scons( + /// scons(Add, scons(3, scons(4, Nil))), + /// Nil + /// ) + /// ) + /// ); /// /// let exp = exp.step().unwrap(); - /// assert_eq!(exp, scons(Mul, scons(2, 6))); + /// assert_eq!(exp, scons(Mul, scons(2, scons(7, Nil)))); /// /// let exp = exp.step().unwrap(); - /// assert_eq!(exp, Atom(Int(12))); + /// assert_eq!(exp, Atom(Int(14))); /// } /// /// ``` pub fn step(self) -> Result<Self, String> { match self { - SCons(op, e1) if *op == Atom(Add) - || *op == Atom(Sub) - || *op == Atom(Mul) - || *op == Atom(Div) => match *e1 { - SCons(e1, e2) => match (*e1, *e2) { - (Atom(Int(a)), Atom(Int(b))) => match *op { - Atom(Add) => Ok(Atom(Int(a + b))), - Atom(Sub) => Ok(Atom(Int(a - b))), - Atom(Mul) => Ok(Atom(Int(a * b))), - Atom(Div) => Ok(Atom(Int(a / b))), - _ => panic!("unreachable as per match arm"), + // List processing + + // op not a value + // ----------------------- + // (op x) -> (op.step() x) + SCons(op, l) if !(*op).is_value() => Ok(scons(op.step()?, l)), + + + // op value and a1 .. an values, and b0, b1 .. bn not values + // --------------------------------------------------------- + // (op a1 ... an b) -> (op a1 ... an b0.step() b1 .. bn) + SCons(op, l) if !(*l).consists_of_values() => { + fn inner(s: SExp) -> Result<SExp, String> { + match s { + SCons(a, b) if (*a).is_value() => Ok(scons(a, inner(*b)?)), + SCons(a, b) => Ok(scons(a.step()?, b)), + x => x.step() + } + } + Ok(scons(op, inner(*l)?)) + }, + + + // Arithmetic + + + // t1, ..., tn integers + // ------------------------------ + // (+ t1 ... tn) -> t1 + ... + tn + SCons(op, l) if *op == Atom(Add) => l.into_vec()? + .iter() + .fold( + Ok(0), + |acc, el| { + match el { + Int(x) => acc.map(|v| x+v), + _ => Err( + "'+' should only be given integers".to_string() + ) + } + } + ).map(|x| Atom(Int(x))), + + + // t1, ..., tn integers + // ------------------------------ + // (* t1 ... tn) -> t1 * ... * tn + SCons(op, l) if *op == Atom(Mul) => l.into_vec()? + .iter() + .fold( + Ok(1), + |acc, el| { + match el { + Int(x) => acc.map(|v| x*v), + _ => Err( + format!("'*' should only be given integers, found {:?}", el) + ) } - (Atom(Int(a)), e2) => Ok(scons(op, scons(a, e2.step()?))), - (e1, e2) => Ok(scons(op, scons(e1.step()?, e2))), - }, - _ => Err(format!("{:?} should be given two arguments, only one was given: {:?}", op, e1)), + } + ).map(|x| Atom(Int(x))), + + + // t1,t2 integers + // -------------------- + // (- t1 t2) -> t1 - t2 + SCons(op, l) if *op == Atom(Sub) => { + let ls = l.into_vec()?; + if ls.len() == 2 { + match (ls[0].clone(), ls[1].clone()) { + (Int(a), Int(b)) => Ok(Atom(Int(a - b))), + _ => Err("'-' should be only given integers".to_string()) + } + } else { + Err(format!("'-' should be given 2 arguments, found {}", ls.len())) + } }, + + // t1,t2 integers + // -------------------- + // (/ t1 t2) -> t1 / t2 + SCons(op, l) if *op == Atom(Div) => { + let ls = l.into_vec()?; + if ls.len() == 2 { + match (ls[0].clone(), ls[1].clone()) { + (_, Int(0)) => Err("division by zero".to_string()), + (Int(a), Int(b)) => Ok(Atom(Int(a / b))), + _ => Err("'/' should be only given integers".to_string()) + } + } else { + Err(format!("'/' should be given 2 arguments, found {}", ls.len())) + } + }, + + // t is value + // ---------- + // t -> t + t if t.is_value() => Ok(t), + t => Err(format!("unimplemented: {:?}.step()", t)), } } diff --git a/src/sexp/util.rs b/src/sexp/util.rs index ce6de5d..70ac152 100644 --- a/src/sexp/util.rs +++ b/src/sexp/util.rs @@ -1,4 +1,6 @@ +use std::iter; + use crate::sexp::SExp; use crate::sexp::SLeaf; use crate::sexp::SExp::*; @@ -30,3 +32,30 @@ pub fn var(name: &str) -> SExp { Atom(Var(name.to_string())) } +impl SExp { + /// Transforms this SExp into a Vec<SLeaf>. + /// + /// Errors if the left-hand side of SCons has an SCons, + /// or if the passed SExp is an Atom, or if the + /// lists aren't Nil-terminated. + /// + /// ```rust + /// use melisp::sexp::{SExp::*, SLeaf::*, util::*}; + /// assert_eq!( + /// scons(1, scons(2, scons(3, Nil))).into_vec(), + /// Ok(vec![Int(1), Int(2), Int(3)]) + /// ); + /// assert!(scons(scons(1, Nil), Nil).into_vec().is_err()); + /// assert!(scons(1, 2).into_vec().is_err()); + /// ``` + pub fn into_vec(self) -> Result<Vec<SLeaf>, String> { + match self { + SCons(a, b) => match (*a, *b) { + (Atom(a), Atom(Nil)) => Ok(iter::once(a).collect::<Vec<SLeaf>>()), + (Atom(a), b) => Ok(iter::once(a).chain(b.into_vec()?).collect::<Vec<SLeaf>>()), + (a, b) => Err(format!("invalid list structure: {:?}", scons(a, b))) + }, + _ => Err("expected list, found atom".to_string()), + } + } +} |