Skip to content

Commit a930433

Browse files
committed
Respect getOption("keep.source") in ReadConsole parser
wip
1 parent b1dd058 commit a930433

File tree

14 files changed

+127
-40
lines changed

14 files changed

+127
-40
lines changed

crates/ark/src/interface.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ use harp::routines::r_register_routines;
7676
use harp::session::r_traceback;
7777
use harp::srcref::get_block_srcrefs;
7878
use harp::srcref::get_srcref;
79+
use harp::srcref::SrcFile;
7980
use harp::utils::r_is_data_frame;
8081
use harp::utils::r_typeof;
8182
use harp::R_MAIN_THREAD_ID;
@@ -310,7 +311,16 @@ enum ParseResult<T> {
310311

311312
impl PendingInputs {
312313
pub(crate) fn read(input: &str) -> anyhow::Result<ParseResult<PendingInputs>> {
313-
let status = match harp::parse_status(&harp::ParseInput::Text(input)) {
314+
let mut _srcfile = None;
315+
316+
let input = if harp::get_option_bool("keep.source") {
317+
_srcfile = Some(SrcFile::new_virtual_empty_filename(input.into()));
318+
harp::ParseInput::SrcFile(&_srcfile.unwrap())
319+
} else {
320+
harp::ParseInput::Text(input)
321+
};
322+
323+
let status = match harp::parse_status(&input) {
314324
Err(err) => {
315325
// Failed to even attempt to parse the input, something is seriously wrong
316326
// FIXME: There are some valid syntax errors going through here, e.g. `identity |> _(1)`.
@@ -327,7 +337,7 @@ impl PendingInputs {
327337
harp::ParseResult::Complete(exprs) => exprs,
328338
harp::ParseResult::Incomplete => {
329339
return Ok(ParseResult::SyntaxError(format!(
330-
"Can't execute incomplete input:\n{input}"
340+
"Can't parse incomplete input"
331341
)));
332342
},
333343
harp::ParseResult::SyntaxError { message, .. } => {

crates/ark/src/variables/variable.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2112,7 +2112,7 @@ mod tests {
21122112
#[test]
21132113
fn test_truncation_on_matrices() {
21142114
r_task(|| {
2115-
let env = Environment::new_empty().unwrap();
2115+
let env = Environment::new_empty();
21162116
let value = harp::parse_eval_base("matrix(0, nrow = 10000, ncol = 10000)").unwrap();
21172117
env.bind("x".into(), &value);
21182118

@@ -2132,7 +2132,7 @@ mod tests {
21322132
#[test]
21332133
fn test_string_truncation() {
21342134
r_task(|| {
2135-
let env = Environment::new_empty().unwrap();
2135+
let env = Environment::new_empty();
21362136
let value = harp::parse_eval_base("paste(1:5e6, collapse = ' - ')").unwrap();
21372137
env.bind("x".into(), &value);
21382138

@@ -2143,7 +2143,7 @@ mod tests {
21432143
assert_eq!(vars[0].is_truncated, true);
21442144

21452145
// Test for the empty string
2146-
let env = Environment::new_empty().unwrap();
2146+
let env = Environment::new_empty();
21472147
let value = harp::parse_eval_base("''").unwrap();
21482148
env.bind("x".into(), &value);
21492149

@@ -2157,7 +2157,7 @@ mod tests {
21572157
#[test]
21582158
fn test_s4_with_different_length() {
21592159
r_task(|| {
2160-
let env = Environment::new_empty().unwrap();
2160+
let env = Environment::new_empty();
21612161
// Matrix::Matrix objects have length != 1, but their format() method returns a length 1 character
21622162
// describing their class.
21632163
let value = harp::parse_eval_base("Matrix::Matrix(0, nrow= 10, ncol = 10)").unwrap();
@@ -2181,7 +2181,7 @@ mod tests {
21812181
return;
21822182
}
21832183

2184-
let env = Environment::new_empty().unwrap();
2184+
let env = Environment::new_empty();
21852185
let value = harp::parse_eval_base(r#"rlang:::chr_get("foo", 0L)"#).unwrap();
21862186
env.bind("x".into(), &value);
21872187

@@ -2196,7 +2196,7 @@ mod tests {
21962196
fn test_matrix_display() {
21972197
r_task(|| {
21982198
// Test 10x10 matrix
2199-
let env = Environment::new_empty().unwrap();
2199+
let env = Environment::new_empty();
22002200
let value =
22012201
harp::parse_eval_base("matrix(paste(1:90, collapse = ' - '), nrow = 9, ncol = 10)")
22022202
.unwrap();
@@ -2220,7 +2220,7 @@ mod tests {
22202220
assert_eq!(display_value_matrix, display_value_df);
22212221

22222222
// Test plurals
2223-
let env = Environment::new_empty().unwrap();
2223+
let env = Environment::new_empty();
22242224
let value =
22252225
harp::parse_eval_base("matrix(paste(1:100, collapse = ' - '), nrow = 1, ncol = 1)")
22262226
.unwrap();
@@ -2231,7 +2231,7 @@ mod tests {
22312231
assert_eq!(vars[0].display_value, "[1 row x 1 column] <matrix>");
22322232

22332233
// Test class
2234-
let env = Environment::new_empty().unwrap();
2234+
let env = Environment::new_empty();
22352235
let value = harp::parse_eval_base(
22362236
"structure(matrix(paste(1:100, collapse = ' - '), nrow = 1, ncol = 1), class='foo')",
22372237
)

crates/ark/tests/kernel-notebook.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ fn test_notebook_execute_request_incomplete() {
7272

7373
assert!(frontend
7474
.recv_iopub_execute_error()
75-
.contains("Can't execute incomplete input"));
75+
.contains("Can't parse incomplete input"));
7676

7777
frontend.recv_iopub_idle();
7878

@@ -95,7 +95,7 @@ fn test_notebook_execute_request_incomplete_multiple_lines() {
9595

9696
assert!(frontend
9797
.recv_iopub_execute_error()
98-
.contains("Can't execute incomplete input"));
98+
.contains("Can't parse incomplete input"));
9999

100100
frontend.recv_iopub_idle();
101101

crates/ark/tests/kernel.rs

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fn test_execute_request_incomplete() {
6060
frontend.execute_request_invisibly("options(positron.error_entrace = FALSE)");
6161

6262
frontend.execute_request_error("1 +", |error_msg| {
63-
assert_eq!(error_msg, "Error:\nCan't execute incomplete input:\n1 +");
63+
assert_eq!(error_msg, "Error:\nCan't parse incomplete input");
6464
});
6565
}
6666

@@ -69,7 +69,7 @@ fn test_execute_request_incomplete_multiple_lines() {
6969
let frontend = DummyArkFrontend::lock();
7070

7171
frontend.execute_request_error("1 +\n2 +", |error_msg| {
72-
assert!(error_msg.contains("Can't execute incomplete input"));
72+
assert!(error_msg.contains("Can't parse incomplete input"));
7373
});
7474
}
7575

@@ -229,7 +229,7 @@ fn test_execute_request_browser_incomplete() {
229229
let input = frontend.recv_iopub_execute_input();
230230
assert_eq!(input.code, code);
231231

232-
frontend.recv_iopub_stream_stderr("Error: Can't execute incomplete input:\n1 +\n");
232+
frontend.recv_iopub_stream_stderr("Error: Can't parse incomplete input\n");
233233
frontend.recv_iopub_idle();
234234

235235
assert_eq!(frontend.recv_shell_execute_reply(), input.execution_count);
@@ -938,3 +938,37 @@ fn test_shutdown_request_while_busy() {
938938

939939
DummyArkFrontend::wait_for_cleanup();
940940
}
941+
942+
#[test]
943+
fn test_execute_request_source_references() {
944+
let frontend = DummyArkFrontend::lock();
945+
946+
// Test that our parser attaches source references when global option is set
947+
frontend.execute_request_invisibly("options(keep.source = TRUE)");
948+
frontend.execute_request_invisibly("f <- function() {}");
949+
950+
frontend.execute_request(
951+
"srcref <- attr(f, 'srcref'); inherits(srcref, 'srcref')",
952+
|result| {
953+
assert_eq!(result, "[1] TRUE");
954+
},
955+
);
956+
957+
frontend.execute_request(
958+
"srcfile <- attr(srcref, 'srcfile'); inherits(srcfile, 'srcfile')",
959+
|result| {
960+
assert_eq!(result, "[1] TRUE");
961+
},
962+
);
963+
964+
// When global option is unset, we don't attach source references
965+
frontend.execute_request_invisibly("options(keep.source = FALSE)");
966+
frontend.execute_request_invisibly("g <- function() {}");
967+
968+
frontend.execute_request(
969+
"srcref <- attr(g, 'srcref'); identical(srcref, NULL)",
970+
|result| {
971+
assert_eq!(result, "[1] TRUE");
972+
},
973+
);
974+
}

crates/harp/.zed/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../../../.zed/settings.json

crates/harp/src/environment.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ impl Environment {
5656
Self::new_filtered(env, EnvironmentFilter::default())
5757
}
5858

59-
pub fn new_empty() -> anyhow::Result<Self> {
60-
Ok(Self::new(harp::parse_eval_base(
61-
"new.env(parent = emptyenv())",
62-
)?))
59+
/// Creates hashed environment of default size inheriting from the empty
60+
/// environment
61+
pub fn new_empty() -> Self {
62+
// Passing `size = 0` causes default size to be picked up
63+
let env = unsafe { libr::R_NewEnv(R_ENVS.empty, 1, 0) };
64+
Self::new(RObject::new(env))
6365
}
6466

6567
pub fn new_filtered(env: RObject, filter: EnvironmentFilter) -> Self {

crates/harp/src/environment_iter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ mod tests {
189189
#[allow(non_snake_case)]
190190
fn test_binding_eq() {
191191
r_task(|| {
192-
let env: Environment = Environment::new_empty().unwrap();
192+
let env: Environment = Environment::new_empty();
193193

194194
let obj = harp::parse_eval_base("1").unwrap();
195195
env.bind(RSymbol::from("a"), &obj);

crates/harp/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//
22
// lib.rs
33
//
4-
// Copyright (C) 2023 Posit Software, PBC. All rights reserved.
4+
// Copyright (C) 2025 Posit Software, PBC. All rights reserved.
55
//
66
//
7+
78
pub mod attrib;
89
pub mod call;
910
mod column_names;
@@ -24,6 +25,7 @@ pub mod line_ending;
2425
mod matrix;
2526
pub mod modules;
2627
pub mod object;
28+
pub mod options;
2729
pub mod parse;
2830
pub mod parser;
2931
pub mod polled_events;
@@ -61,6 +63,7 @@ pub use vector::list::*;
6163
// resolve to the correct symbols
6264
extern crate self as harp;
6365

66+
pub use harp::environment::*;
6467
pub use harp::error::as_result;
6568
pub use harp::exec::top_level_exec;
6669
pub use harp::exec::try_catch;
@@ -72,7 +75,7 @@ pub use harp::object::list_poke;
7275
pub use harp::object::RObject;
7376
pub use harp::session::*;
7477
pub use harp::symbol::RSymbol;
75-
pub use harp::utils::get_option;
78+
pub use harp::options::*;
7679
pub use harp::weak_ref::RWeakRef;
7780
pub use harp_macros::register;
7881

crates/harp/src/options.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//
2+
// options.rs
3+
//
4+
// Copyright (C) 2025 Posit Software, PBC. All rights reserved.
5+
//
6+
//
7+
8+
use crate::{r_symbol, RObject};
9+
10+
pub fn get_option(name: &str) -> RObject {
11+
unsafe { libr::Rf_GetOption1(r_symbol!(name)).into() }
12+
}
13+
14+
pub fn get_option_bool(name: &str) -> bool {
15+
harp::get_option(name).try_into().unwrap_or(false)
16+
}

crates/harp/src/parse.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub enum ParseResult {
3131
SyntaxError { message: String },
3232
}
3333

34+
#[derive(Clone, Debug)]
3435
pub enum ParseInput<'a> {
3536
Text(&'a str),
3637
SrcFile(&'a srcref::SrcFile),
@@ -67,7 +68,7 @@ pub fn parse_exprs(text: &str) -> crate::Result<RObject> {
6768

6869
/// Same but creates srcrefs
6970
pub fn parse_exprs_with_srcrefs(text: &str) -> crate::Result<RObject> {
70-
let srcfile = srcref::SrcFile::try_from(text)?;
71+
let srcfile = srcref::SrcFile::from(text);
7172
parse_exprs_ext(&ParseInput::SrcFile(&srcfile))
7273
}
7374

@@ -84,7 +85,7 @@ pub fn parse_exprs_ext<'a>(input: &ParseInput<'a>) -> crate::Result<RObject> {
8485
}
8586

8687
pub fn parse_with_parse_data(text: &str) -> crate::Result<(ParseResult, ParseData)> {
87-
let srcfile = srcref::SrcFile::try_from(text)?;
88+
let srcfile = srcref::SrcFile::from(text);
8889

8990
// Fill parse data in `srcfile` by side effect
9091
let status = parse_status(&ParseInput::SrcFile(&srcfile))?;

0 commit comments

Comments
 (0)