aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Kronqvist <joel.kronqvist@iki.fi>2025-07-30 17:48:11 +0300
committerJoel Kronqvist <joel.kronqvist@iki.fi>2025-07-30 17:48:11 +0300
commita4b09d523e0b500de29c08b32abc13af8e509299 (patch)
tree77e0bdfa5b39c0789e829e698846fb06becbf7e1
parentd2d4d5cffdce0ce28e59732f56a771f8fc3431de (diff)
parentcd68a2880db1400ae09ce0df64994b2bae33a3c1 (diff)
downloadmyslip-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.rs20
-rw-r--r--src/sexp/step.rs172
-rw-r--r--src/sexp/util.rs29
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()),
+ }
+ }
+}