From 790a749cf35fd132524fb7bd92b72b9efa297c32 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Sat, 12 Oct 2024 21:28:06 +0100 Subject: [PATCH] Publish a post about Valeri+org-babel --- .../note.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 content/posts/valeri_notebook_programming_with_org_babel/note.md diff --git a/content/posts/valeri_notebook_programming_with_org_babel/note.md b/content/posts/valeri_notebook_programming_with_org_babel/note.md new file mode 100644 index 0000000..eb17101 --- /dev/null +++ b/content/posts/valeri_notebook_programming_with_org_babel/note.md @@ -0,0 +1,76 @@ +X-Date: 2024-10-12T20:12:24Z +X-Note-Id: 087d89cd-27ec-43d4-9d46-b48eed3ce301 +Subject: Valeri notebook programming with Org Babel +X-Slug: valeri_notebook_programming_with_org_babel + +I really wanted to get an interactive programming environment for [Valeri](https://git.knazarov.com/knazarov/valeri) +which works in a similar fashion to [Jupyter notebooks](https://jupyter.org/). 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](https://orgmode.org/worg/org-contrib/babel/intro.html), which is an extension to Emacs +[org-mode](https://orgmode.org/) allowing the execution of code snippets within the Org markup code blocks. + +The [implementation](https://git.knazarov.com/knazarov/valeri/src/branch/master/emacs) 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](https://en.wikipedia.org/wiki/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: +: # + +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.