Skip to content

Commit 5c13c49

Browse files
authored
fix(lang): function calling and promises
Internal changes: * `eval_list_eager()` was removed from the `Context` trait and added as a member method for `CallStack` (because we need a callstack to force arguments). * `eval_list_lazy()` now boxes all expressions in promises (including literals). This is necessary to box `..a`-style ellipsis arguments in a list-call promise, which requires access to the underlying expression (needed to solve #216). Bugs: * `substitute()` now works on datatypes such as literals or calls (#199). * accessing variable collected via 'rest-args' does now force evaluation of calls (#216).
1 parent 6c5e304 commit 5c13c49

File tree

10 files changed

+152
-62
lines changed

10 files changed

+152
-62
lines changed

src/CHANGELOG.md

+9
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@
1414
* The `typeof()` primitive was added
1515
* Type stability for numeric operations (@69)
1616

17+
## Noteable Bugs Addressed:
18+
19+
* `substitute()` now works on datatypes such as literals or calls (#199).
20+
* accessing variable collected via 'rest-args' does now force evaluation of calls (#216).
21+
1722
## Internals
1823

1924
* The `List` is now represented as a `Rep<Obj>`, unifying heterogenous and atomic vectors.
2025
This included a considerable refactor.
2126
* Iterating over references of a `Rep<T>` was made much simpler and new methods were added
2227
and unused ones removed.
2328
* The `RepType` struct that was introduced in 0.4.0 was removed again (#189).
29+
* `eval_list_eager()` was removed from the `Context` trait and added as a member method for `CallStack`.
30+
* `eval_list_lazy()` now boxes all expressions in promises (including literals).
31+
This is necessary to box `..a`-style ellipsis arguments in a list-call promise, which requires
32+
access to the underlying expression (needed to solve #216).
2433

2534
## Notable Bugs Addressed
2635

src/callable/core.rs

+25-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,30 @@ pub trait Callable: CallableFormals {
8383
}
8484
}
8585

86+
let mut ellipsis_expr = ExprList::new();
87+
88+
for (k, v) in ellipsis.iter_pairs() {
89+
if let Obj::Promise(_, e, _) = v {
90+
ellipsis_expr.push_named(k.as_option(), e)
91+
} else {
92+
// all arguments must be boxed in promises to allow for NSE
93+
unreachable!()
94+
}
95+
}
96+
97+
let list = crate::callable::builtins::BUILTIN
98+
.get("list")
99+
.cloned()
100+
.unwrap();
101+
102+
// convert the expr_ellipsis to an Obj::Promise where the expression is a call into List
103+
104+
let ellipsis_promise = Obj::Promise(
105+
None,
106+
Expr::Call(Box::new(Expr::Primitive(list)), ellipsis_expr),
107+
stack.last_frame().env().clone(),
108+
);
109+
86110
// add back in parameter defaults that weren't filled with args
87111
for (param, default) in formals.into_iter() {
88112
matched_args.push_named(
@@ -92,7 +116,7 @@ pub trait Callable: CallableFormals {
92116
}
93117

94118
if let Some(Expr::Ellipsis(Some(name))) = remainder.get(0) {
95-
matched_args.push_named(Character::Some(name), Obj::List(ellipsis.clone()));
119+
matched_args.push_named(Character::Some(name), ellipsis_promise);
96120
} else if !remainder.is_empty() {
97121
matched_args.push_named(
98122
Character::Some("...".to_string()),

src/callable/primitive/c.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use r_derive::*;
22

33
use crate::callable::core::*;
4-
use crate::context::Context;
54
use crate::object::types::*;
65
use crate::object::*;
76
use crate::{formals, lang::*};

src/callable/primitive/list.rs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use r_derive::*;
22

33
use crate::callable::core::*;
4-
use crate::context::Context;
54
use crate::formals;
65
use crate::lang::*;
76
use crate::object::*;

src/callable/primitive/quote.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ use crate::object::*;
2525
///
2626
/// ```custom,{class=r-repl}
2727
/// quote(x + y)
28+
/// quote(1)
2829
/// ```
29-
///
30+
/// ## Differentes to the R implementation
31+
/// While R treats literals as expressions this implementation of `quote` differentiates between
32+
/// the literal `1` and the length-1 vector "`c(1)`".
33+
/// Thereby the return type of `quote()` can be expected to be an object of type `Expression`.
3034
#[doc(alias = "quote")]
3135
#[builtin(sym = "quote")]
3236
#[derive(Debug, Clone, PartialEq)]
@@ -39,3 +43,14 @@ impl Callable for PrimitiveQuote {
3943
Ok(Obj::Expr(args.get(0).unwrap_or(Expr::Null)))
4044
}
4145
}
46+
47+
#[cfg(test)]
48+
49+
mod tests {
50+
use crate::{r, r_expect};
51+
52+
#[test]
53+
fn literals_dont_evaluate() {
54+
r_expect!(typeof(quote(1)) == "expression")
55+
}
56+
}

src/callable/primitive/substitute.rs

+16-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ impl Callable for PrimitiveSubstitute {
126126

127127
#[cfg(test)]
128128
mod test {
129-
use crate::r;
129+
use crate::{r, r_expect};
130130

131131
#[test]
132132
fn function_param_promises() {
@@ -191,4 +191,19 @@ mod test {
191191
r! { quote(3 + 4) }
192192
);
193193
}
194+
195+
#[test]
196+
fn literals_evaluate_to_themselves() {
197+
r_expect!(substitute(1) == 1);
198+
r_expect!(substitute("a") == "a");
199+
r_expect!(substitute(true) == true);
200+
r_expect!(substitute(1L) == 1L);
201+
}
202+
203+
#[test]
204+
fn calls_work() {
205+
r_expect! {{"
206+
eval(substitute(fn(x) x))(1) == 1
207+
"}}
208+
}
194209
}

src/callable/primitive/type_reflection.rs

+1-16
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,7 @@ impl Callable for PrimitiveTypeOf {
1616
fn call_matched(&self, args: List, _ellipsis: List, stack: &mut CallStack) -> EvalResult {
1717
let mut args = Obj::List(args);
1818
let x = args.try_get_named("x")?.force(stack)?;
19-
20-
let t = match x {
21-
Obj::Null => "null",
22-
Obj::Vector(v) => match v {
23-
Vector::Character(_) => "character",
24-
Vector::Integer(_) => "integer",
25-
Vector::Double(_) => "double",
26-
Vector::Logical(_) => "logical",
27-
},
28-
Obj::List(_) => "list",
29-
Obj::Expr(_) => "expression",
30-
Obj::Promise(..) => "promise",
31-
Obj::Function(..) => "function",
32-
Obj::Environment(..) => "environment",
33-
};
34-
EvalResult::Ok(Obj::Vector(Vector::Character(vec![t.to_string()].into())))
19+
EvalResult::Ok(Obj::Vector(Vector::Character(vec![x.type_of()].into())))
3520
}
3621
}
3722

src/context/core.rs

+2-41
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use std::rc::Rc;
22

33
use crate::lang::{EvalResult, Signal};
4-
use crate::object::types::Character;
54
use crate::object::*;
65
use crate::{error::*, internal_err};
76

@@ -88,7 +87,7 @@ pub trait Context: std::fmt::Debug + std::fmt::Display {
8887
internal_err!()
8988
}
9089
}
91-
// Avoid creating a new closure just to point to another, just reuse it
90+
// Avoid creating a new promise just to point to another, just reuse it
9291
(k, Expr::Symbol(s)) => match self.env().get(s.clone()) {
9392
Ok(c @ Obj::Promise(..)) => {
9493
let k = k.map_or(OptionNA::NA, OptionNA::Some);
@@ -109,46 +108,8 @@ pub trait Context: std::fmt::Debug + std::fmt::Display {
109108
Ok(List::from(elem).iter_pairs())
110109
}
111110
(k, v) => {
112-
let k = k.map_or(OptionNA::NA, OptionNA::Some);
113-
if let Ok(elem) = self.eval(v) {
114-
Ok(List::from(vec![(k, elem)]).iter_pairs())
115-
} else {
116-
internal_err!()
117-
}
118-
}
119-
})
120-
.collect::<Result<Vec<_>, _>>()?
121-
.into_iter()
122-
.flatten()
123-
.collect::<Vec<_>>(),
124-
)))
125-
}
126-
127-
fn eval_list_eager(&mut self, l: ExprList) -> EvalResult {
128-
Ok(Obj::List(List::from(
129-
l.into_iter()
130-
.map(|pair| match pair {
131-
(_, Expr::Ellipsis(None)) => {
132-
if let Ok(Obj::List(ellipsis)) = self.get_ellipsis() {
133-
Ok(ellipsis.iter_pairs())
134-
} else {
135-
Ok(List::from(Vec::<(Character, Obj)>::new()).iter_pairs())
136-
}
111+
Ok(List::from(vec![(k, Obj::Promise(None, v, self.env()))]).iter_pairs())
137112
}
138-
(_, Expr::Ellipsis(Some(name))) => {
139-
if let Ok(Obj::List(more)) = self.get(name) {
140-
Ok(more.iter_pairs())
141-
} else {
142-
Ok(List::from(Vec::<(Character, Obj)>::new()).iter_pairs())
143-
}
144-
}
145-
(k, v) => match self.eval_and_finalize(v) {
146-
Ok(elem) => {
147-
let k = k.map_or(OptionNA::NA, OptionNA::Some);
148-
Ok(List::from(vec![(k, elem)]).iter_pairs())
149-
}
150-
Err(e) => Err(e),
151-
},
152113
})
153114
.collect::<Result<Vec<_>, _>>()?
154115
.into_iter()

src/lang.rs

+76
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,23 @@ impl ViewMut for Obj {
7979
}
8080

8181
impl Obj {
82+
pub fn type_of(&self) -> String {
83+
match self {
84+
Obj::Null => "null",
85+
Obj::Vector(v) => match v {
86+
Vector::Character(_) => "character",
87+
Vector::Integer(_) => "integer",
88+
Vector::Double(_) => "double",
89+
Vector::Logical(_) => "logical",
90+
},
91+
Obj::List(_) => "list",
92+
Obj::Expr(_) => "expression",
93+
Obj::Promise(..) => "promise",
94+
Obj::Function(..) => "function",
95+
Obj::Environment(..) => "environment",
96+
}
97+
.to_string()
98+
}
8299
pub fn with_visibility(self, visibility: bool) -> EvalResult {
83100
Signal::Return(self, visibility).into()
84101
}
@@ -633,6 +650,50 @@ pub struct CallStack {
633650
}
634651

635652
impl CallStack {
653+
pub fn eval_list_eager(&mut self, l: ExprList) -> EvalResult {
654+
Ok(Obj::List(List::from(
655+
l.into_iter()
656+
.map(|pair| match pair {
657+
(_, Expr::Ellipsis(None)) => {
658+
if let Ok(Obj::List(ellipsis)) = self.get_ellipsis() {
659+
// Each iterator yields a ()
660+
let x = ellipsis
661+
.iter_pairs()
662+
.map(|(k, v)| match v {
663+
Obj::Promise(_, expr, _) => {
664+
Ok((k, self.eval_and_finalize(expr)?))
665+
}
666+
_ => Ok((k, v)),
667+
})
668+
.collect::<Result<Vec<(Character, Obj)>, Signal>>()?;
669+
let l = List::from(x);
670+
Ok(l.iter_pairs())
671+
} else {
672+
Ok(List::from(Vec::<(Character, Obj)>::new()).iter_pairs())
673+
}
674+
}
675+
(_, Expr::Ellipsis(Some(name))) => {
676+
if let Ok(Obj::List(more)) = self.get(name) {
677+
Ok(more.iter_pairs())
678+
} else {
679+
Ok(List::from(Vec::<(Character, Obj)>::new()).iter_pairs())
680+
}
681+
}
682+
(k, v) => match self.eval_and_finalize(v) {
683+
Ok(elem) => {
684+
let k = k.map_or(OptionNA::NA, OptionNA::Some);
685+
Ok(List::from(vec![(k, elem)]).iter_pairs())
686+
}
687+
Err(e) => Err(e),
688+
},
689+
})
690+
.collect::<Result<Vec<_>, _>>()?
691+
.into_iter()
692+
.flatten()
693+
.collect::<Vec<_>>(),
694+
)))
695+
}
696+
636697
pub fn parse(&self, input: &str) -> ParseResult {
637698
let config: SessionParserConfig = self.session.clone().into();
638699
config.parse_input(input)
@@ -1220,6 +1281,21 @@ mod test {
12201281
)
12211282
}
12221283

1284+
#[test]
1285+
fn accessing_ellipsis_forces_evaluation() {
1286+
assert_eq!(
1287+
CallStack::default()
1288+
.map_session(|s| s.with_experiments(vec![Experiment::RestArgs]))
1289+
.parse_and_eval(
1290+
"
1291+
f = fn(...) { . }
1292+
f(sum(1))
1293+
",
1294+
),
1295+
r! { list(1) }
1296+
)
1297+
}
1298+
12231299
#[test]
12241300
fn fn_duplicated_parameters() {
12251301
assert_eq!(

src/object/ast.rs

+7
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ pub struct ExprList {
100100
pub values: Vec<Expr>,
101101
}
102102

103+
impl ExprList {
104+
pub fn push_named(&mut self, key: Option<String>, value: Expr) {
105+
self.keys.push(key);
106+
self.values.push(value);
107+
}
108+
}
109+
103110
impl fmt::Display for ExprList {
104111
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105112
let pairs: Vec<String> = self

0 commit comments

Comments
 (0)