Valeri notebook programming with Org Babel
I really wanted to get an interactive programming environment for Valeri which works in a similar fashion to Jupyter notebooks. It is of course possible to add the support for the Valeri kernel to Jupyter itself, but it's a lot of work. So I opted for Org Babel, which is an extension to Emacs org-mode allowing the execution of code snippets within the Org markup code blocks.
The implementation turned out to be quite simple: in just a few hundred lines of code, I've got support for running the Valeri REPL as a background "inferior" process in Emacs. The rest just involved writing some glue code to add a bridge from Babel to send the code body to the REPL and getting the text results back. Most of the code handling the interation with the inferior REPL is already in Emacs base, so you don't have to write it on your own.
If you exclude all the glue code, this is what the implementation boils down to:
(defun org-babel-valeri-evaluate (buffer body)
"Pass BODY to the Valeri process in BUFFER."
(let ((escaped-body (format "\x1b[200~%s\x1b[201~" body))
(eoe-string (format "\n(println \"%s\")\n" valeri-eoe-indicator)))
(mapconcat
#'identity
(butlast
(split-string
(mapconcat
#'org-trim
(org-babel-comint-with-output
(buffer valeri-eoe-indicator t escaped-body)
(insert (org-babel-chomp escaped-body) eoe-string)
(comint-send-input nil t))
"\n") "[\r\n]")) "\n")
)
)
It just takes the body
string, wraps it into the bracketed paste escape sequence, sends to the REPL buffer, reads the result back and trims unnecessary symbols.
The usage of this integration is quite simple as well. You just create a file with .org
extension that can look like this:
* Notebook programming with Valeri
The following code block will create a new persistent
interpreter session and load the factorial function
there.
#+begin_src valeri :session val
(fn fact (n)
(if (<= n 0)
1
(* n (fact (- n 1)))))
#+end_src
#+RESULTS:
: #<function fact>
And this code block will run the factorial function
and output the result.
#+begin_src valeri :session val
(println "12! is" (fact 12))
#+end_src
#+RESULTS:
: 12! is 479001600
If you position the cursor in the begin_src/end_src
block and press Ctrl-C Ctrl-C, it will be evaluated and the #+RESULTS
will be updated. What is also convenient is that multiple code blocks can share a session, so you can define a function in the first block and call it in the subsequent block.