Skip to content

Commit e5b1a4a

Browse files
authored
Merge pull request #254 from posit-dev/feature/r-call
Extract `RCall` from `RFunction`
2 parents 24746bb + 3727d10 commit e5b1a4a

File tree

5 files changed

+142
-121
lines changed

5 files changed

+142
-121
lines changed

crates/ark/src/variables/variable.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use amalthea::comm::variables_comm::ClipboardFormatFormat;
99
use amalthea::comm::variables_comm::Variable;
1010
use amalthea::comm::variables_comm::VariableKind;
11-
use harp::call::RCall;
1211
use harp::environment::Binding;
1312
use harp::environment::BindingValue;
1413
use harp::environment::Environment;
@@ -497,8 +496,7 @@ impl PositronVariable {
497496
Ok(RSymbol::new_unchecked(code).to_string())
498497
},
499498
LANGSXP => {
500-
let code = RCall::new(code)?;
501-
let fun = RSymbol::new(CAR(*code))?;
499+
let fun = RSymbol::new(CAR(code))?;
502500
if fun == "lazyLoadDBfetch" {
503501
return Ok(String::from("(unevaluated)"))
504502
}

crates/ark/tests/help.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,12 @@ fn test_help_comm() {
8282
assert_eq!(handled, false);
8383

8484
// Figure out which port the R help server is running on (or would run on)
85-
let r_help_port =
86-
r_task(|| unsafe { RFunction::new("tools", "httpdPort").call()?.to::<u16>() }).unwrap();
85+
let r_help_port = r_task(|| unsafe {
86+
RFunction::new_internal("tools", "httpdPort")
87+
.call()?
88+
.to::<u16>()
89+
})
90+
.unwrap();
8791

8892
// Send a request to show a help URL with a valid help URL. This one should
8993
// be handled.

crates/harp/src/call.rs

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,77 @@
55
//
66
//
77

8-
use std::ops::Deref;
8+
use libr::*;
99

10-
use libr::LANGSXP;
11-
use libr::SEXP;
12-
13-
use crate::error::Result;
10+
use crate::exec::RFunction;
11+
use crate::exec::RFunctionExt;
1412
use crate::object::RObject;
15-
use crate::utils::r_assert_type;
13+
use crate::r_symbol;
14+
use crate::utils::r_typeof;
1615

1716
pub struct RCall {
18-
pub object: RObject,
17+
function: RObject,
18+
arguments: Vec<RArgument>,
1919
}
2020

2121
impl RCall {
22-
pub fn new_unchecked(object: impl Into<RObject>) -> Self {
22+
pub fn new(function: impl Into<RObject>) -> Self {
2323
Self {
24-
object: object.into(),
24+
function: function.into(),
25+
arguments: Vec::new(),
2526
}
2627
}
2728

28-
pub fn new(object: impl Into<RObject>) -> Result<Self> {
29-
let object = object.into();
30-
r_assert_type(*object, &[LANGSXP])?;
31-
Ok(Self::new_unchecked(object))
29+
pub fn param(&mut self, name: &str, value: impl Into<RObject>) -> &mut Self {
30+
self.arguments.push(RArgument {
31+
name: name.to_string(),
32+
value: value.into(),
33+
});
34+
self
35+
}
36+
37+
pub fn add(&mut self, value: impl Into<RObject>) -> &mut Self {
38+
self.param("", value)
39+
}
40+
41+
pub fn build(&self) -> RObject {
42+
unsafe {
43+
let call = RObject::new(Rf_lcons(self.function.sexp, R_NilValue));
44+
let mut tail = call.sexp;
45+
46+
// Append arguments to the call
47+
for argument in self.arguments.iter() {
48+
SETCDR(tail, Rf_cons(argument.value.sexp, R_NilValue));
49+
50+
tail = CDR(tail);
51+
if !argument.name.is_empty() {
52+
SET_TAG(tail, r_symbol!(argument.name));
53+
}
54+
}
55+
56+
call
57+
}
3258
}
3359
}
3460

35-
impl Deref for RCall {
36-
type Target = SEXP;
37-
fn deref(&self) -> &Self::Target {
38-
&self.object
61+
pub fn r_expr_quote(x: impl Into<SEXP>) -> RObject {
62+
let x = x.into();
63+
match r_typeof(x) {
64+
SYMSXP | LANGSXP => return RFunction::new("base", "quote").add(x).call.build(),
65+
_ => return x.into(),
3966
}
4067
}
4168

42-
impl From<RCall> for RObject {
43-
fn from(value: RCall) -> Self {
44-
value.object
69+
pub struct RArgument {
70+
pub name: String,
71+
pub value: RObject,
72+
}
73+
74+
impl RArgument {
75+
pub fn new(name: &str, value: RObject) -> Self {
76+
Self {
77+
name: name.to_string(),
78+
value,
79+
}
4580
}
4681
}

crates/harp/src/exec.rs

Lines changed: 75 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::os::raw::c_void;
1212

1313
use libr::*;
1414

15+
use crate::call::RCall;
1516
use crate::environment::R_ENVS;
1617
use crate::error::Error;
1718
use crate::error::Result;
@@ -25,126 +26,67 @@ use crate::r_string;
2526
use crate::r_symbol;
2627
use crate::utils::r_inherits;
2728
use crate::utils::r_stringify;
28-
use crate::utils::r_typeof;
2929
use crate::vector::CharacterVector;
3030
use crate::vector::Vector;
31-
pub struct RArgument {
32-
pub name: String,
33-
pub value: RObject,
34-
}
35-
36-
impl RArgument {
37-
pub fn new(name: &str, value: RObject) -> Self {
38-
Self {
39-
name: name.to_string(),
40-
value,
41-
}
42-
}
43-
}
4431

4532
pub struct RFunction {
46-
package: String,
47-
function: String,
48-
arguments: Vec<RArgument>,
33+
pub call: RCall,
34+
is_namespaced: bool,
4935
}
5036

51-
pub trait RFunctionExt<T> {
52-
fn param(&mut self, name: &str, value: T) -> &mut Self;
53-
fn add(&mut self, value: T) -> &mut Self;
54-
}
37+
impl RFunction {
38+
pub fn new(package: &str, function: &str) -> Self {
39+
Self::new_ext(package, function, false)
40+
}
5541

56-
impl<T: Into<RObject>> RFunctionExt<Option<T>> for RFunction {
57-
fn param(&mut self, name: &str, value: Option<T>) -> &mut Self {
58-
if let Some(value) = value {
59-
self._add(name, value.into());
60-
}
61-
self
42+
pub fn new_internal(package: &str, function: &str) -> Self {
43+
Self::new_ext(package, function, true)
6244
}
6345

64-
fn add(&mut self, value: Option<T>) -> &mut Self {
65-
if let Some(value) = value {
66-
self._add("", value.into());
46+
pub fn new_inlined(function: impl Into<RObject>) -> Self {
47+
RFunction {
48+
call: RCall::new(function),
49+
is_namespaced: false,
6750
}
68-
self
6951
}
70-
}
7152

72-
impl<T: Into<RObject>> RFunctionExt<T> for RFunction {
73-
fn param(&mut self, name: &str, value: T) -> &mut Self {
74-
let value: RObject = value.into();
75-
return self._add(name, value);
76-
}
53+
fn new_ext(package: &str, function: &str, internal: bool) -> Self {
54+
unsafe {
55+
let is_namespaced = !package.is_empty();
7756

78-
fn add(&mut self, value: T) -> &mut Self {
79-
let value: RObject = value.into();
80-
return self._add("", value);
81-
}
82-
}
57+
let fun = if is_namespaced {
58+
let op = if internal { ":::" } else { "::" };
59+
Rf_lang3(r_symbol!(op), r_symbol!(package), r_symbol!(function))
60+
} else {
61+
r_symbol!(function)
62+
};
63+
let fun = RObject::new(fun);
8364

84-
impl RFunction {
85-
pub fn new(package: &str, function: &str) -> Self {
86-
RFunction {
87-
package: package.to_string(),
88-
function: function.to_string(),
89-
arguments: Vec::new(),
65+
RFunction {
66+
call: RCall::new(fun),
67+
is_namespaced,
68+
}
9069
}
9170
}
9271

93-
fn _add(&mut self, name: &str, value: RObject) -> &mut Self {
94-
self.arguments.push(RArgument {
95-
name: name.to_string(),
96-
value,
97-
});
98-
self
99-
}
100-
10172
pub fn call(&mut self) -> Result<RObject> {
102-
let env = if self.package.is_empty() {
103-
R_ENVS.global
104-
} else {
73+
// FIXME: Once we have ArkFunction (see
74+
// https://github.com/posit-dev/positron/issues/2324), we no longer need
75+
// this logic to call in global. This probably shouldn't be the default?
76+
let env = if self.is_namespaced {
10577
R_ENVS.base
78+
} else {
79+
R_ENVS.global
10680
};
10781

10882
self.call_in(env)
10983
}
11084

11185
pub fn call_in(&mut self, env: SEXP) -> Result<RObject> {
11286
unsafe {
113-
let mut protect = RProtect::new();
114-
115-
// start building the call to be evaluated
116-
let mut lhs = r_symbol!(self.function);
117-
if !self.package.is_empty() {
118-
lhs = protect.add(Rf_lang3(r_symbol!(":::"), r_symbol!(self.package), lhs));
119-
}
120-
121-
// now, build the actual call to be evaluated
122-
let size = (1 + self.arguments.len()) as R_xlen_t;
123-
let call = protect.add(Rf_allocVector(LANGSXP, size));
124-
SET_TAG(call, R_NilValue);
125-
SETCAR(call, lhs);
126-
127-
// append arguments to the call
128-
let mut slot = CDR(call);
129-
for argument in self.arguments.iter() {
130-
// quote language objects by default
131-
let mut sexp = argument.value.sexp;
132-
if matches!(r_typeof(sexp), LANGSXP | SYMSXP | EXPRSXP) {
133-
let quote = protect.add(Rf_lang3(
134-
r_symbol!("::"),
135-
r_symbol!("base"),
136-
r_symbol!("quote"),
137-
));
138-
sexp = protect.add(Rf_lang2(quote, sexp));
139-
}
87+
let call = self.call.build();
14088

141-
SETCAR(slot, sexp);
142-
if !argument.name.is_empty() {
143-
SET_TAG(slot, r_symbol!(argument.name));
144-
}
145-
146-
slot = CDR(slot);
147-
}
89+
let mut protect = RProtect::new();
14890

14991
// now, wrap call in tryCatch, so that errors don't longjmp
15092
let try_catch = protect.add(Rf_lang3(
@@ -154,7 +96,7 @@ impl RFunction {
15496
));
15597
let call = protect.add(Rf_lang4(
15698
try_catch,
157-
call,
99+
call.sexp,
158100
r_symbol!("identity"),
159101
r_symbol!("identity"),
160102
));
@@ -187,6 +129,41 @@ impl From<String> for RFunction {
187129
}
188130
}
189131

132+
// NOTE: Having to import this trait cause a bit of friction during
133+
// development. Can we do without?
134+
pub trait RFunctionExt<T> {
135+
fn param(&mut self, name: &str, value: T) -> &mut Self;
136+
fn add(&mut self, value: T) -> &mut Self;
137+
}
138+
139+
impl<T: Into<RObject>> RFunctionExt<Option<T>> for RFunction {
140+
fn param(&mut self, name: &str, value: Option<T>) -> &mut Self {
141+
if let Some(value) = value {
142+
self.call.param(name, value.into());
143+
}
144+
self
145+
}
146+
147+
fn add(&mut self, value: Option<T>) -> &mut Self {
148+
if let Some(value) = value {
149+
self.call.add(value.into());
150+
}
151+
self
152+
}
153+
}
154+
155+
impl<T: Into<RObject>> RFunctionExt<T> for RFunction {
156+
fn param(&mut self, name: &str, value: T) -> &mut Self {
157+
self.call.param(name, value);
158+
self
159+
}
160+
161+
fn add(&mut self, value: T) -> &mut Self {
162+
self.call.add(value);
163+
self
164+
}
165+
}
166+
190167
pub fn geterrmessage() -> String {
191168
// SAFETY: Returns pointer to static memory buffer owned by R.
192169
let buffer = unsafe { R_curErrorBuf() };
@@ -495,6 +472,9 @@ pub fn r_source_exprs_in(exprs: impl Into<SEXP>, env: impl Into<SEXP>) -> crate:
495472
let exprs = exprs.into();
496473
let env = env.into();
497474

475+
// `exprs` is an EXPRSXP and doesn't need to be quoted when passed as
476+
// literal argument. Only the R-level `eval()` function evaluates expression
477+
// vectors.
498478
RFunction::new("base", "source")
499479
.param("exprs", exprs)
500480
.param("local", env)
@@ -681,6 +661,7 @@ mod tests {
681661
use crate::r_test;
682662
use crate::utils::r_envir_remove;
683663
use crate::utils::r_is_null;
664+
use crate::utils::r_typeof;
684665

685666
#[test]
686667
fn test_basic_function() {

crates/harp/src/utils.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ use libr::*;
1414
use once_cell::sync::Lazy;
1515
use regex::Regex;
1616

17+
use crate::call::r_expr_quote;
18+
use crate::call::RArgument;
1719
use crate::environment::Environment;
1820
use crate::environment::R_ENVS;
1921
use crate::error::Error;
2022
use crate::error::Result;
2123
use crate::eval::r_parse_eval0;
2224
use crate::exec::geterrmessage;
23-
use crate::exec::RArgument;
2425
use crate::exec::RFunction;
2526
use crate::exec::RFunctionExt;
2627
use crate::object::RObject;
@@ -401,7 +402,9 @@ pub unsafe fn r_stringify(object: SEXP, delimiter: &str) -> Result<String> {
401402
}
402403

403404
// call format on the object
404-
let object = RFunction::new("base", "format").add(object).call()?;
405+
let object = RFunction::new("base", "format")
406+
.add(r_expr_quote(object))
407+
.call()?;
405408

406409
// paste into a single string
407410
let object = RFunction::new("base", "paste")

0 commit comments

Comments
 (0)