diff --git a/Cargo.lock b/Cargo.lock index a1ae9074..5ae09e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -412,7 +412,7 @@ dependencies = [ [[package]] name = "r" -version = "0.1.0" +version = "0.1.1" dependencies = [ "crossterm 0.27.0", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 54d8c445..175d1de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "r" -version = "0.1.0" +version = "0.1.1" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/numerics.rs b/examples/numerics.rs index 8db412b1..b44b04d2 100644 --- a/examples/numerics.rs +++ b/examples/numerics.rs @@ -1,4 +1,4 @@ -use r::r_vector::vectors::*; +use r::vector::vectors::*; fn main() { let x = Vector::from(vec![false, true, false]); @@ -8,7 +8,7 @@ fn main() { println!("{} + {}", x, y); println!("{}\n", x + y); - use r::r_vector::vectors::OptionNA::*; // Some() and NA + use r::vector::vectors::OptionNA::*; // Some() and NA let x = Vector::from(vec![Some(5_i32), NA, Some(1_i32)]); let y = Vector::from(vec![1_f64, 2_f64, 3_f64]); diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md new file mode 100644 index 00000000..71768bd1 --- /dev/null +++ b/src/CHANGELOG.md @@ -0,0 +1,54 @@ +# 0.2.0 "In Bloom" + +## Major Changes + +* Primitives are now more consistently handled, allowing them to be reassigned + like any other function object. Prior to these enhancements, primitives + would only be used for calls to specificly named symbols. + + ```r + f <- paste + f("a", c("b", "c")) + # [1] "a b" "a c" + ``` + +* A call stack with proper call frames is now added, paving the way for + improved error messages and the implementation of R's metaprogramming tools. + + You can view the call stack by introducing a `callstack()` call: + + ```r + f <- function(...) list(..., callstack()) + f("Hello, World!") + # [[1]] + # [1] "Hello, World!" + # + # [[2]] + # [[2]][[1]] + # f("Hello, World!") + # + # [[2]][[2]] + # list(..., callstack()) + # + # [[2]][[3]] + # callstack() + ``` + +* Even more primitives now implemented! This release brings `paste()` and + `callstack()` (akin to R's `sys.calls()`) + +## Behind the Scenes + +* Primitives are now _all_ implemented as `dyn Primitive` objects, implementing + a `Callable` trait. They still don't have a proper standard library namespace, + and are discovered only if not found in the environment (or its parents), + but this paves the way for treating primitives more like user-defined + functions. + +## Thanks to our new contributors! + +@armenic (#16) + +# 0.1.0 "Why Not?" + +Initial release. diff --git a/src/callable/primitive/paste.rs b/src/callable/primitive/paste.rs index f0bf4e00..b9350107 100644 --- a/src/callable/primitive/paste.rs +++ b/src/callable/primitive/paste.rs @@ -15,10 +15,12 @@ impl PrimitiveSYM for PrimitivePaste { impl Callable for PrimitivePaste { fn call(&self, args: ExprList, stack: &mut CallStack) -> EvalResult { - let R::List(mut vals) = stack.parent_frame().eval_list_greedy(args)? else { + let R::List(vals) = stack.parent_frame().eval_list_lazy(args)? else { unreachable!() }; + let mut vals = force_closures(vals, stack); + let mut sep = String::from(" "); let mut should_collapse = false; let mut collapse = String::new(); @@ -101,142 +103,107 @@ impl Callable for PrimitivePaste { #[cfg(test)] mod test_primitive_paste { use super::*; - use crate::parser::parse_args; + use crate::parser::parse; #[test] fn test_primitive_paste_01() { - let mut env = Environment::default(); - let args = parse_args("paste(1, 2, collapse = NULL)").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); + let mut stack = CallStack::new(); + let expr = parse("paste(1, 2, collapse = NULL)").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); let expected: Vec<_> = vec!["1 2"]; - assert_eq!(observed, expected); } #[test] fn test_primitive_paste_02() { - let mut env = Environment::default(); - let args = parse_args("paste(null)").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); + let mut stack = CallStack::new(); + let expr = parse("paste(null)").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); let expected: Vec<&str> = vec![]; - assert_eq!(observed, expected); } #[test] fn test_primitive_paste_03() { - let mut env = Environment::default(); - let args = parse_args("paste(1.1, null, 2, false, 'a', c(1.0, 2.0), sep = '+')").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1.1++2+false+a+1", "1.1++2+false+a+2"] - .iter() - .map(|s| s.to_string()) - .collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(1.1, null, 2, false, 'a', c(1.0, 2.0), sep = '+')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1.1++2+false+a+1".to_string(), "1.1++2+false+a+2".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_04() { - let mut env = Environment::default(); - let args = parse_args("paste(1.1, null, 2, false, 'a', c(1.0, 2.0))").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1.1 2 false a 1", "1.1 2 false a 2"] - .iter() - .map(|s| s.to_string()) - .collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(1.1, null, 2, false, 'a', c(1.0, 2.0))").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1.1 2 false a 1".to_string(), "1.1 2 false a 2".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_05() { - let mut env = Environment::default(); - let args = - parse_args("paste(c(1, 2, 3, 4, 5), c('st', 'nd', 'rd', c('th', 'th')), sep = '')") - .unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1st", "2nd", "3rd", "4th", "5th"] - .iter() - .map(|s| s.to_string()) - .collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(c(1, 2, 3, 4, 5), c('st', 'nd', 'rd', c('th', 'th')), sep = '')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1st".to_string(), "2nd".to_string(), "3rd".to_string(), "4th".to_string(), "5th".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_06() { - let mut env = Environment::default(); - let args = - parse_args("paste(1.1, null, 2, false, 'a', c(1.0, 2.0), , collapse = '+')").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1.1 2 false a 1+1.1 2 false a 2"] - .iter() - .map(|s| s.to_string()) - .collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(1.1, null, 2, false, 'a', c(1.0, 2.0), , collapse = '+')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1.1 2 false a 1+1.1 2 false a 2".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_07() { - let mut env = Environment::default(); - let args = parse_args("paste(1, 2, 3, collapse = '+')").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1 2 3"].iter().map(|s| s.to_string()).collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(1, 2, 3, collapse = '+')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1 2 3".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_08() { - let mut env = Environment::default(); - let args = parse_args("paste(c(1, 2), 3, 4, 5, sep = '-', collapse = '+')").unwrap(); - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["1-3-4-5+2-3-4-5"] - .iter() - .map(|s| s.to_string()) - .collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste(c(1, 2), 3, 4, 5, sep = '-', collapse = '+')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["1-3-4-5+2-3-4-5".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_09() { - let mut env = Environment::default(); - // Passing x vector to the environment so that paste can use it - env.insert( - "x".to_string(), - R::Vector(Vector::Character(vec![OptionNA::Some( - "".to_string(), - )])), - ); - let args = parse_args("paste(c('a', 'b'), collapse = x)").unwrap(); - - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["ab"].iter().map(|s| s.to_string()).collect(); - + let mut stack = CallStack::new(); + let x_val = stack.eval(parse("\"\"").unwrap()).unwrap(); + stack.last_frame().env.insert("x".to_string(), x_val); + let expr = parse("paste(c('a', 'b'), collapse = x)").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["ab".to_string()]; assert_eq!(observed, expected); } #[test] fn test_primitive_paste_10() { - let mut env = Environment::default(); - let args = parse_args("paste('a', c('b', 'c'), collapse = '')").unwrap(); - - let R::Vector(observed) = PrimitivePaste.call(args, &mut env).unwrap() else {unimplemented!()}; - let observed: Vec<_> = observed.into(); - let expected: Vec<_> = vec!["a ba c"].iter().map(|s| s.to_string()).collect(); - + let mut stack = CallStack::new(); + let expr = parse("paste('a', c('b', 'c'), collapse = '')").unwrap(); + let R::Vector(res) = stack.eval(expr).unwrap() else { unimplemented!() }; + let observed: Vec<_> = res.into(); + let expected: Vec<_> = vec!["a ba c".to_string()]; assert_eq!(observed, expected); } } diff --git a/src/lang.rs b/src/lang.rs index fda180aa..e7484368 100644 --- a/src/lang.rs +++ b/src/lang.rs @@ -467,6 +467,10 @@ impl CallStack { error => error } } + + pub fn new() -> CallStack { + CallStack::from(Frame { call: Expr::Missing, env: Rc::new(Environment::default())}) + } } impl Display for CallStack { @@ -596,7 +600,7 @@ pub trait Context { } } }) - .collect(), + .collect() )) } } @@ -651,6 +655,13 @@ impl Context for CallStack { self.last_frame().eval(expr) } } + + // NOTE: + // eval_list_lazy and force_closures are often used together to greedily + // evaluated arguments. This pattern can be specialized in the case of a + // CallStack to cut out the creation of intermediate closures. Need to + // lift EvalResult over Context::eval_list_lazy's flat_map by implementing + // Try. } impl Context for &Frame { diff --git a/src/repl/repl.rs b/src/repl/repl.rs index 3e8e853a..33c8405c 100644 --- a/src/repl/repl.rs +++ b/src/repl/repl.rs @@ -14,7 +14,7 @@ where P: AsRef, { // print session header - println!("R version 0.0.1 -- \"Why Not?\""); + println!("R version 0.2.0 -- \"In Bloom\""); let line_editor = Reedline::create() .with_validator(Box::new(RValidator)) diff --git a/src/vector/iterators.rs b/src/vector/iterators.rs index c4d04f7e..4c80dbf4 100644 --- a/src/vector/iterators.rs +++ b/src/vector/iterators.rs @@ -6,7 +6,7 @@ use crate::vector::coercion::*; /// recycled even if they do not repeat an even number of times. /// /// ```rust -/// use r::r_vector::iterators::zip_recycle; +/// use r::vector::iterators::zip_recycle; /// /// let x = vec![1, 2, 3, 4]; /// let y = vec![2, 4];