Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Add code evaluation API#1085

Merged
jmcphers merged 10 commits intomainfrom
feature/evaluate-code-api
Mar 9, 2026
Merged

Add code evaluation API#1085
jmcphers merged 10 commits intomainfrom
feature/evaluate-code-api

Conversation

@jmcphers
Copy link
Contributor

@jmcphers jmcphers commented Mar 4, 2026

This change adds an API for evaluating code to Ark.

Most of the change is actually just getting smarter about type coercion, so that named character vectors get serialized into JSON objects instead of losing their names.

Part of posit-dev/positron#7112.

@jmcphers jmcphers requested a review from DavisVaughan March 4, 2026 23:06
Copy link
Contributor

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few thoughts about the new handling for character vectors. Although it is defensive regarding the presence of empty names, I'm a bit concerned that simple changes to the R representation (a name slips in) results in a completely different json representation. Also since the mapping from named vector to json map is lossy when there are duplicate names, this could lead to surprising behaviour or bugs in extension code.

But more generally, since jsonlite is the most popular serialisation package, I think it would be good to follow what it is doing, to follow the principle of least surprise.

  • Lists: Array/Object depending on presence of names (as we currently do). This suffers from type unstability for pragmatic reasons.
  • Vectors: Always as arrays, names are ignored. To get an Object, use as.list().


#[test]
#[allow(non_snake_case)]
fn test_json_named_character_vectors() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing tests:

  • Empty character vector (empty array)
  • Character vector of size one (still an array, not a scalar)
  • Character vector with NA names (which core R methods sometimes introduce)
  • Character vector with duplicate names, should test that last one wins.

Comment on lines +254 to +256
harp::Error::TryCatchError { message, .. } => message.clone(),
harp::Error::ParseError { message, .. } => message.clone(),
harp::Error::ParseSyntaxError { message } => message.clone(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the clones can be avoided if you match on err instead of &err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right!

let names = obj.names()?;
let all_empty = names.iter().all(|name| match name {
Some(name) => name.is_empty(),
None => true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NA names become None here IIUC? This would be the expected behaviour, just checking (and should also be tested).

Comment on lines +453 to +454
/// The result of evaluating the code
EvaluateCodeReply(serde_json::Value),
Copy link
Contributor

@lionel- lionel- Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way there's one command that I was using all the time in Emacs and that I'd like to implement in Positron. It's a version of "evaluate statement" that inserts the output of the evaluation below the statement, with #> prefix.

IIUC it's not currently possible to implement this from an extension because the evaluation needs to happen in the background (silently) but the output should still be captured and returned to the caller. It looks like that use case is not covered by posit-dev/positron#7112.

To support this case, perhaps we could consider returning a structured value with result and output fields. The output field could be opt-in by language in case the kernel can't easily support it. See #1064 for an example of how to evaluate with output and condition capture.

In any case, wrapping the return value in a struct would be more future-proof, allowing new fields to be added as needed.

@jmcphers jmcphers requested a review from lionel- March 5, 2026 19:11
@jmcphers
Copy link
Contributor Author

jmcphers commented Mar 5, 2026

@lionel- I implemented an output capture system, and also rolled back the changes to the JSON serializer. Thanks for reviewing!

let result = r_task(|| {
let evaluated = parse_eval_global(&params.code)?;
Value::try_from(evaluated)
// Set up a raw connection to capture printed output via sink().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Console::start_capture() instead?

Warnings are a complication to be aware of, as by defaults they are emitted when R goes back to idle.

I realised in https://github.com/posit-dev/ark/pull/1064/changes#r2896192009 that the warning capture I've implemented for conditional breakpoints can be simplified by having the output capture guard set options(warn = 1) while output is being captured. This causes warnings to be emitted immediately instead of being buffered by R. So if you switch to the console capture mechanism, it should take care of warnings once that change is merged.

Warnings will behave more like messages and regular output in that they will be interleaved in the captured output, but I think that's acceptable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impl in 9ccdd0d, let me know what you think. The console isn't available in unit tests, so I added some tests closer to the integration layer (i.e. using a "UI Comm").

@jmcphers jmcphers requested a review from lionel- March 6, 2026 18:22
Copy link
Contributor

@DavisVaughan DavisVaughan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I'll let @lionel- have the final say since he had more comments about it!

Comment on lines +259 to +260
let output = capture.take();
drop(capture);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lionel- it might be nice to have capture.consume() which returns a String like take() but also consumes self and drops it - like a one shot capture so you don't have to think about dropping as much.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, or capture.into_string()?

The explicit drop is a clearer signal of the side effect though.

Copy link
Contributor

@lionel- lionel- left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LG!

Comment on lines +259 to +260
let output = capture.take();
drop(capture);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, or capture.into_string()?

The explicit drop is a clearer signal of the side effect though.

@lionel-
Copy link
Contributor

lionel- commented Mar 9, 2026

If you'd like you can rebase on main to make a test with warning capture. The Console output capture mechanism now automatically sets the option to emit warnings immediately instead of letting R buffer them.

@jmcphers jmcphers merged commit 0907b9a into main Mar 9, 2026
9 checks passed
@jmcphers jmcphers deleted the feature/evaluate-code-api branch March 9, 2026 16:47
@github-actions github-actions bot locked and limited conversation to collaborators Mar 9, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants