r-wasm / webr

The statistical language R compiled to WebAssembly via Emscripten, for use in web browsers and Node.
https://docs.r-wasm.org/webr/latest/
Other
805 stars 54 forks source link

`\n` in `cat()` chops off the later portion of the string #375

Closed coatless closed 3 months ago

coatless commented 4 months ago

Consider:

cat("firstly\nsecondly")

We would expect the output to be:

firstly
secondly

Instead, the output is:

firstly

However, if we place another cat with sep = "\n" after, we get:

secondlyseparatefirstly
secondly

Demo in REPL:

Examining the `cat` output bug

Code:

cat("firstly\nsecondly")
cat("separate")

cat("firstly", "secondly", sep="\n")

/ cc @ute and c.f. https://github.com/coatless/quarto-webr/issues/133

georgestagg commented 4 months ago

From a certain viewpoint, this is expected. It's a consequence of how Emscripten's design line buffers output by default. Buffering line output works better with the behaviour of JavaScript tools such as console.log() where a partial line cannot be emitted.

Since the line secondly does not end in a newline character, the output is buffered and not actually emitted until the next \n is encountered in the output[^1].

I suppose there is a "solution" in that you could set things up so that a newline is always emitted after evaluating code, using R functions like cat("\n"). However, it is also possible to flush the output buffers directly by using the Module._fsync() function in the worker thread.

For example, by tweaking webR's prompt handling so that the buffers are flushed whenever a prompt is emitted using something like

webr::eval_js("
  const pr = Module.setPrompt;
  Module.setPrompt = (prompt) => { Module._fsync(); pr(prompt); }
")

The example then has the behaviour you want:

> webr::eval_js("const pr = Module.setPrompt; Module.setPrompt = (prompt) => { Module._fsync(); pr(prompt); }")
[1] 0
> cat("firstly\nsecondly")
firstly
secondly
> 

Admittedly, modifying internal undocumented webR functions is not a good solution if you'd like to opt-in to this non-default behaviour. I will have a think about how best to expose Module._fsync() to the main thread, and if an option to directly enable this behaviour should be available at webR init time.

EDIT: Possibly the right place for this might be in captureR()/evalR() rather than flushing during the R prompt. I could be convinced that in that case we really should flush the output by default after the code has been evaluated.

[^1]: You can also see this kind of strange behaviour happening with R in a terminal using your example. At least on my machine the second line of output is secondly>, where the R prompt has been written directly after the output without a newline.

coatless commented 4 months ago

@georgestagg I appreciate the depth regarding what's going on. One portion of the explanation yielded a great insight:

I suppose there is a "solution" in that you could set things up so that a newline is always emitted after evaluating code, using R functions like cat("\n").

After chatting with another R dev, I was reminded that we could use fill = TRUE to trigger the cat("\n") behavior, e.g.

cat("firstly\nsecondly", fill = TRUE)
show `fill = TRUE` in `cat()` leading to the second line not being cut off

This is probably a better option (albeit it requires documentation) due to the complexity with exposing Module._fsync(). So, I think I'm advocating to avoid adding the functionality now that I know what's going on.

georgestagg commented 3 months ago

So, I think I'm advocating to avoid adding the functionality now that I know what's going on.

Okay, I'll close the issue for now. We can always revisit and document things somewhere if others hit the same issue.

SugarRayLua commented 2 months ago

@georgestagg,

I think there now might be something more that has gone wrong with cat():

I can't get cat() to generate any output (see attached)-- tested on iOS (iPad: mobile Safari and Chrome).

Fyi IMG_1836

georgestagg commented 1 month ago

Hi @SugarRayLua,

The change in https://github.com/r-wasm/webr/issues/412 only applies to use of the webR.captureR() API. For the webR console shown in your screenshot, the discussion above still applies:

Since the line secondly does not end in a newline character, the output is buffered and not actually emitted until the next \n is encountered in the output

e.g. Screenshot 2024-05-07 at 10 43 54


So, you can either explicitly add a \n to the end of your string, or use the cat() argument fill = TRUE:

Screenshot 2024-05-07 at 10 44 18

SugarRayLua commented 1 month ago

Okay, thank you. 😊