Skip to content

Repository files navigation

nimteractive

A persistent warm Nim session for interactive use from Emacs org-babel and Jupyter.

The Problem

Nim is fast — but cold-starting a script that imports datamancer or arraymancer means paying generic instantiation and macro expansion costs on every single run. Interactive data exploration becomes painful when each tweak takes seconds to compile.

nimteractive solves this by keeping a session's growing script (imports + procs + history) warm against a persistent, shared Nim build cache, so only the newest block's delta needs to be recompiled.

Architecture

┌──────────────────────────────────────────────────────────────────┐
│  SESSION  (imports + procs + history, replayed on every eval)    │
│                                                                    │
│  Each eval block appends to history and is compiled for real:    │
│    write .nim → `nim c --app:lib` against a persistent nimcache  │
│    (keyed by sha256(imports | nim version | nimble.lock)) →      │
│    dlopen the .so (its NimMain runs as a POSIX constructor,      │
│    executing all top-level statements and producing stdout) →    │
│    dlclose immediately                                           │
└──────────────────────────────────────────────────────────────────┘

Because the whole script (imports+procs+history+new code) compiles and runs as one program each time, cross-block let/var state works naturally — no VM, no typed value store, no cross-library ABI. The trade-off: eval pays a real compile+link, not an instant VM step — sub-second once the nimcache is warm for your imports, not zero. :precompile blocks front-load the expensive part (datamancer/ arraymancer-style generic instantiation and C compilation) once; regular eval blocks only pay for their own delta.

Installation

1. Build the binary

cd nimteractive
nim c -o:nimteractive src/nimteractive.nim
# or via nimble:
nimble build

Put the resulting nimteractive binary somewhere on your PATH (e.g. ~/.nimble/bin/).

2. Install the Emacs package

With use-package and the built-in :vc fetcher (Emacs 30+):

(use-package ob-nimteractive
  :vc (:url "~/nimteractive" :lisp-dir ".")
  :config
  (ob-nimteractive-setup))

Or load the file directly:

(load "~/nimteractive/ob-nimteractive.el")
(ob-nimteractive-setup)

ob-nimteractive-setup registers nim with org-babel and pins *Nimteractive:* buffers to a bottom side window (like ESS does for *R*).

Emacs Usage

org-babel blocks

Three block types, selected by header arguments:

1. Precompile — run once per session. Compiles your imports and caches the result.

#+begin_src nim :session analysis :precompile t
import strutils
#+end_src

Output: [session ready — 4200ms] on first run, [session ready — 80ms, cached] thereafter.

2. Procs — define reusable procedures. Re-execute to redefine (this clears accumulated eval history, same as re-running :precompile).

#+begin_src nim :session analysis :procs t
proc shout(s: string): string =
  s.toUpperAscii & "!"
#+end_src

3. Eval — interactive code. State accumulates across blocks.

#+begin_src nim :session analysis
let msg = shout("hello, nim")
echo msg
#+end_src

Interactive session buffer

Each session gets a *Nimteractive:<name>* comint buffer — a live REPL backed by the same process that org-babel uses.

Key Action
C-c C-v z Jump to (or open) the session buffer
RET Send current input as an eval request

With a prefix argument (C-u C-c C-v z) you are prompted for a session name — useful when working with multiple named sessions.

When you open a nim source block with C-c ' (org-edit-src), the session buffer is automatically displayed alongside the edit buffer, mirroring the ESS workflow.

Session management

;; Show session buffer (interactive)
M-x ob-nimteractive-show-session

;; Clean shutdown (sends exit op)
M-x ob-nimteractive-exit-session

;; Hard kill
M-x ob-nimteractive-kill-session

Configuration

;; Path to binary (auto-detected from PATH / ~/.nimble/bin/)
(setq ob-nimteractive-binary "/path/to/nimteractive")

;; Prompt string (must match what the server emits)
(setq ob-nimteractive-prompt "nim> ")

;; Seconds to wait for a response before timing out
(setq ob-nimteractive-timeout 120)

Protocol

The server communicates over stdin/stdout using line-delimited JSON. You can drive it from any editor or script.

→ {"op": "eval",       "id": "1", "code": "echo 1+1"}
← {"op": "result",     "id": "1", "stdout": "2\n", "value": ""}

→ {"op": "precompile", "id": "2", "imports": ["strutils"]}
← {"op": "compiling",  "id": "2"}
← {"op": "ready",      "id": "2", "cache_hit": false, "elapsed_ms": 4200}

→ {"op": "load_procs", "id": "3", "code": "proc shout(s: string): string = s.toUpperAscii & \"!\""}
← {"op": "compiling",  "id": "3"}
← {"op": "reloaded",   "id": "3", "elapsed_ms": 1100}

→ {"op": "complete",   "id": "4", "code": "df[\"", "cursor": 4}
← {"op": "completions","id": "4", "items": []}

→ {"op": "exit",       "id": "5"}
← {"op": "result",     "id": "5", "stdout": "bye", "value": ""}

Cache

Cache key: sha256(sorted_imports | nim_version | nimble.lock)

Stored under ~/.cache/nimteractive/<hash>/:

Path Contents
nimcache/ Nim's incremental build cache — persists across evals, so a datamancer-heavy delta compile stays fast
tmp/<tag>.nim, tmp/<tag>.so per-eval generated source and library, removed right after each dlclose

The cache invalidates automatically when you upgrade Nim or change package versions.

Requirements

  • Nim ≥ 2.2.0
  • Emacs ≥ 29.1, Org ≥ 9.6 (for the Emacs integration)

License

MIT

About

Core for creating responsive Nim REPLs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages