From 0c5d14ed8ed75842df6ce02d854db57864596cd3 Mon Sep 17 00:00:00 2001 From: Ava Hahn Date: Tue, 7 Mar 2023 21:27:45 -0800 Subject: [PATCH] add mod and exp functions, tests, and also some snippets for common patterns in the readme --- Readme.org | 76 ++++++++++++++++++---- src/stl.rs | 22 +++++++ src/stl/math.rs | 96 ++++++++++++++++++++++++++++ tests/test_lib_math.rs | 142 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 323 insertions(+), 13 deletions(-) diff --git a/Readme.org b/Readme.org index 2c2f183..d354564 100644 --- a/Readme.org +++ b/Readme.org @@ -37,17 +37,67 @@ https://matrix.to/#/#vomitorium:matrix.sunnypup.io **** TODO Anatomy **** TODO Naming conventions **** TODO Undefining variables and functions -*** TODO Easy patterns -**** TODO while-let combo -**** TODO main loop application -**** TODO let destructuring -**** TODO if-set? -*** TODO Builtin functions -*** TODO Documentation -**** TODO Tests -**** TODO Help function -**** TODO Env function -**** TODO Snippets directory +** Easy patterns +This section can serve as a sort of cookbook for a user who is new to leveraging LISP languages or unsure of where to start with ~relish~. +More ideas may be explored in the file:snippets directory of this project. +The author encourages any users to contribute their own personal favorites not already in this section either by adding them to the file:snippets folder, or to extend the documentation here. +*** while-let combo +#+BEGIN_SRC lisp + ;; myiter = (1 (2 3 4 5 6)) + (def myiter 'iterator over a list' (head (1 2 3 4 5 6))) + + ;; iterate over each element in mylist + (while (gt? (len (cdr myiter)) 0) ;; while there are more elements to consume + (let ((elem (car myiter)) ;; elem = consumed element from myiter + (remaining (cdr myiter))) ;; remaining = rest of elements + (echo elem) ;; do a thing with the element, could be any operation + (def myiter (head remaining)))) ;; consume next element, loop +#+END_SRC + +The while-let pattern can be used for many purposes. Above it is used to iterate over elements in a list. It can also be used to receive connections to a socket and write data to them. +*** TODO main loop application +- state switch (while-toggle) +- state calculation +*** TODO short-circuit guard +- circuit example +- while-not-circuit-do-more-work +*** let destructuring +~let~ is very useful for destructuring complex return types. If you have a function that may return a whole list of values you can then call it from ~let~ to consume the result data. +In this example a let form is used to destructure a call to ~head~. ~head~ returns a list consisting of ~(first-element rest-of-list)~ (for more information see ~(help head)~). +The ~let~ form starts with the output of ~head~ stored in ~head-struct~ (short for head-structured). The next variables defined are ~first~ and ~rest~ which contain individual elements from the return of the call to ~head~. +Finally, the bodies evaluated in the ~let~ form are able to operate on the head and the rest. + #+BEGIN_SRC lisp + ;; individually access the top of a list + (let ((head-struct (head (1 2 3)) + (first (car head-struct)) + (rest (cdr head-struct))) + (echo "this is 1: " first) + (echo "this is 2, 3: " rest)) + #+END_SRC +*** if-set? +One common pattern seen in bash scripts and makefiles is the set-variable-if-not-set pattern. +#+BEGIN_SRC shell + MYVAR ?= MY_SPECIAL_VALUE +#+END_SRC +Translated, can be seen below +#+BEGIN_SRC lisp + (if (set? myvar) + () ;; no need to do anything... or add a call here + (def myvar "MY_SPECIAL_VALUE")) +#+END_SRC + +Alternatively this combination can be used to process flags in a script or application: +#+BEGIN_SRC lisp + (if (set? myflag) + (process-flag myflag) + ()) +#+END_SRC +** TODO Builtin functions +*** TODO Env function +** TODO Documentation +*** TODO Tests +*** TODO Help function +*** TODO Snippets directory ** Configuration By default Relish will read from ~/.relishrc for configuration, but the default shell will also accept a filename from the RELISH_CFG_FILE environment variable. @@ -158,8 +208,8 @@ Will need a concatenate function for tables **** DONE sub **** DONE div **** DONE mul -**** TODO exp -**** TODO mod +**** DONE exp +**** DONE mod ***** TODO Test for Let Destructuring **** TODO inc **** TODO dec diff --git a/src/stl.rs b/src/stl.rs index 1ca2ff4..fd506fb 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -283,6 +283,28 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { }, ); + syms.insert( + "exp".to_string(), + Symbol { + name: String::from("exp"), + args: Args::Lazy(2), + conditional_branches: false, + docs: math::EXP_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::exp_callback)), + } + ); + + syms.insert( + "mod".to_string(), + Symbol { + name: String::from("mod"), + args: Args::Lazy(2), + conditional_branches: false, + docs: math::MOD_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(math::mod_callback)), + } + ); + Ok(()) } diff --git a/src/stl/math.rs b/src/stl/math.rs index c08cba0..017547f 100644 --- a/src/stl/math.rs +++ b/src/stl/math.rs @@ -167,3 +167,99 @@ pub fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result { Err("float cast only takes an integer or a string".to_string()) } } + +pub const EXP_DOCSTRING: &str = "Takes two args, both expected to be numeric. +Returns the first arg to the power of the second arg. +Does not handle overflow or underflow. + +PANIC CASES: +- arg1 is a float and arg2 is greater than an int32 +- an integer exceeding the size of a float64 is raised to a float power +- an integer is rased to the power of another integer exceeding the max size of an unsigned 32bit integer"; + +pub fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result { + let first = *ast.car.clone(); + if !isnumeric(&first) { + return Err("first argument must be numeric".to_string()); + } + let second: Ctr; + if let Ctr::Seg(ref s) = *ast.cdr { + second = *s.car.clone(); + } else { + return Err("impossible error: needs two arguments".to_string()); + } + if !isnumeric(&second) { + return Err("second argument must be numeric".to_string()); + } + + match first { + Ctr::Float(lf) => match second { + Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))), + Ctr::Integer(ri) => Ok(Ctr::Float(f64::powi(lf, ri as i32))), + _ => Err("exp not implemented for these arguments".to_string()), + }, + Ctr::Integer(li) => match second { + Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))), + Ctr::Integer(ri) => Ok(Ctr::Integer(li.pow(ri as u32))), + _ => Err("exp not implemented for these arguments".to_string()), + }, + + _ => Err("exp not implemented for these arguments".to_string()), + } +} + +pub const MOD_DOCSTRING: &str = "Takes two args, both expected to be numeric. +Returns a list of two values: the modulus and the remainder. +Example: (mod 5 3) -> (1 2) + +PANIC CASES: +- A float is modulo an integer larger than a max f64 +- An integer larger than a max f64 is modulo a float +"; + +pub fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result { + let first = *ast.car.clone(); + if !isnumeric(&first) { + return Err("first argument must be numeric".to_string()); + } + let second: Ctr; + if let Ctr::Seg(ref s) = *ast.cdr { + second = *s.car.clone(); + } else { + return Err("impossible error: needs two arguments".to_string()); + } + if !isnumeric(&second) { + return Err("second argument must be numeric".to_string()); + } + + let mut ret = Seg::new(); + + match first { + Ctr::Float(lf) => match second { + Ctr::Float(rf) => { + ret.append(Box::new(Ctr::Integer((lf / rf) as i128))); + ret.append(Box::new(Ctr::Integer((lf % rf) as i128))); + }, + Ctr::Integer(ri) => { + ret.append(Box::new(Ctr::Integer((lf / ri as f64) as i128))); + ret.append(Box::new(Ctr::Integer((lf % ri as f64) as i128))); + }, + _ => return Err("mod not implemented for these arguments".to_string()), + }, + Ctr::Integer(li) => match second { + Ctr::Float(rf) => { + ret.append(Box::new(Ctr::Integer((li as f64 / rf) as i128))); + ret.append(Box::new(Ctr::Integer((li as f64 % rf) as i128))); }, + Ctr::Integer(ri) => { + ret.append(Box::new(Ctr::Integer(li / ri))); + ret.append(Box::new(Ctr::Integer(li % ri))); + }, + _ => return Err("mod not implemented for these arguments".to_string()), + }, + + _ => return Err("mod not implemented for these arguments".to_string()), + } + + + Ok(Ctr::Seg(ret)) +} diff --git a/tests/test_lib_math.rs b/tests/test_lib_math.rs index 9709853..6811532 100644 --- a/tests/test_lib_math.rs +++ b/tests/test_lib_math.rs @@ -145,4 +145,146 @@ mod math_lib_tests { result.to_string(), ); } + + #[test] + fn test_ii_exp() { + let document = "(exp 7 20)"; + let result = 7i128.pow(20); + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_if_exp() { + let document = "(exp 1 10.2)"; + let result = f64::powf(1f64, 10.2); + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_fi_exp() { + let document = "(exp 1.2 5)"; + let result = f64::powf(1.2, 5f64); + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + format!("{:.5}", result), + ); + } + + #[test] + fn test_ff_exp() { + let document = "(exp 1.3 1.5)"; + let result = f64::powf(1.3, 1.5); + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + assert_eq!( + *eval(&lex(&document.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result.to_string(), + ); + } + + #[test] + fn test_ii_mod() { + let document = "(def test '' (mod 7 3))"; + let check1 = "(car test)"; + let result1 = "2"; + let check2 = "(cdr test)"; + let result2 = "1"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let _ = *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap(); + assert_eq!( + *eval(&lex(&check1.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result1.to_string(), + ); + assert_eq!( + *eval(&lex(&check2.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result2.to_string(), + ); + } + + #[test] + fn test_if_mod() { + let document = "(def test '' (mod 7 3.3))"; + let check1 = "(car test)"; + let result1 = "2"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let _ = *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap(); + assert_eq!( + *eval(&lex(&check1.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result1.to_string(), + ); + } + + #[test] + fn test_fi_mod() { + let document = "(def test '' (mod 7.2 2))"; + let check1 = "(car test)"; + let result1 = "3"; let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let _ = *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap(); + assert_eq!( + *eval(&lex(&check1.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result1.to_string(), + ); + } + + #[test] + fn test_ff_mod() { + let document = "(def test '' (mod 7.2 3.3))"; + let check1 = "(car test)"; + let result1 = "2"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + let _ = *eval(&lex(&document.to_string()).unwrap(), &mut syms).unwrap(); + assert_eq!( + *eval(&lex(&check1.to_string()).unwrap(), &mut syms) + .unwrap() + .to_string(), + result1.to_string(), + ); + } }