feat: add fullscreen TUI and project updates
- New TUI screens: Main Menu, Compile, Run, REPL, AI Generator, AI Settings, Help - AI configuration persisted in ~/.config/cljnim/config.json - Added illwill dependency for terminal UI - Updated experiments, examples, docs, and core modules
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Clojure/Nim Experiments
|
||||
|
||||
Unconventional compilation targets that JVM Clojure cannot do.
|
||||
|
||||
## Projects
|
||||
|
||||
| Experiment | Status | Description |
|
||||
|---|---|---|
|
||||
| `web-target/` | ✅ Working | Clojure → Nim → JavaScript for browsers |
|
||||
| `native-lib/` | ✅ Working | Clojure → Nim → C shared library (.so) |
|
||||
| `wasm-target/` | 🏗️ Skeleton | Clojure → Nim → WASM via Emscripten |
|
||||
|
||||
## web-target — Clojure in the Browser
|
||||
|
||||
```bash
|
||||
cd web-target
|
||||
./build.sh
|
||||
node -e "require('./build/math.js'); console.log(jsSquare(7))"
|
||||
# → 49
|
||||
```
|
||||
|
||||
**Unique:** No JVM, no JavaScript source code — pure Clojure compiled to JS.
|
||||
|
||||
## native-lib — Clojure as a C Library
|
||||
|
||||
```bash
|
||||
cd native-lib
|
||||
./build.sh
|
||||
cd test_client && make && LD_LIBRARY_PATH=../build ./test_client
|
||||
# → c_square(7) = 49
|
||||
# → c_factorial(5) = 120
|
||||
```
|
||||
|
||||
**Unique:** Call Clojure functions from Python, Rust, Go, C via FFI.
|
||||
|
||||
## wasm-target — Clojure at Native Speed in Browser
|
||||
|
||||
```bash
|
||||
cd wasm-target
|
||||
# Install Emscripten first, then:
|
||||
./build.sh
|
||||
# Open www/index.html in browser
|
||||
```
|
||||
|
||||
**Unique:** Clojure running as WebAssembly — faster than ClojureScript, smaller than JVM.
|
||||
|
||||
## Architecture
|
||||
|
||||
All experiments share the same pipeline:
|
||||
|
||||
```
|
||||
Clojure Source (.clj)
|
||||
↓
|
||||
cljnim compile-lib
|
||||
↓
|
||||
Nim Source (.nim) with exported procs
|
||||
↓
|
||||
Target-specific wrapper
|
||||
↓
|
||||
nim c --target=...
|
||||
↓
|
||||
JS / .so / .wasm
|
||||
```
|
||||
|
||||
## Adding a New Experiment
|
||||
|
||||
1. Create a folder: `experiments/my-target/`
|
||||
2. Write Clojure example in `examples/`
|
||||
3. Write Nim wrapper in `my_wrappers.nim`
|
||||
4. Write `build.sh` using `cljnim compile-lib`
|
||||
5. Add row to the table above
|
||||
@@ -0,0 +1,47 @@
|
||||
# Clojure/Nim → Native Shared Library
|
||||
|
||||
Compile Clojure code to a C shared library (`.so` / `.dll` / `.dylib`).
|
||||
|
||||
## Why?
|
||||
|
||||
- **Embed Clojure in Python/Rust/Go/C** via FFI
|
||||
- **Tiny binaries** — no JVM overhead
|
||||
- **True C ABI** — call Clojure functions from any language
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
This generates:
|
||||
- `build/libmath.so` — shared library
|
||||
- `build/math.h` — C header
|
||||
- `build/math.nim` — intermediate Nim source
|
||||
|
||||
## Test from C
|
||||
|
||||
```bash
|
||||
cd test_client
|
||||
make
|
||||
./test_client
|
||||
```
|
||||
|
||||
Expected output:
|
||||
```
|
||||
square(5) = 25
|
||||
add(10, 20) = 30
|
||||
cube(3) = 27
|
||||
factorial(5) = 120
|
||||
```
|
||||
|
||||
## How it works
|
||||
|
||||
1. `cljnim compile-lib` — generates Nim with exported `proc name*(...)`
|
||||
2. `nim c --app:lib --header` — compiles Nim to `.so` + `.h`
|
||||
3. C client links against the `.so` and calls functions
|
||||
|
||||
## Limitations
|
||||
|
||||
- Functions use `CljVal` (Nim ref object) — client must call Nim runtime helpers
|
||||
- `NimMain()` must be called before using the library
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLJNIM="${SCRIPT_DIR}/../../cljnim"
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib"
|
||||
|
||||
mkdir -p "${SCRIPT_DIR}/build"
|
||||
|
||||
echo "=== Step 1: Clojure → Nim ==="
|
||||
"$CLJNIM" compile-lib \
|
||||
"${SCRIPT_DIR}/examples/math.clj" \
|
||||
"${SCRIPT_DIR}/build/math.nim"
|
||||
|
||||
echo "=== Step 2: Nim → Shared Library ==="
|
||||
cd "$SCRIPT_DIR" && nim c --app:lib \
|
||||
--path:"$LIB_PATH" \
|
||||
-d:release \
|
||||
-o:"build/libmath.so" \
|
||||
"native_wrappers.nim"
|
||||
|
||||
echo "=== Step 3: Generated files ==="
|
||||
ls -la "${SCRIPT_DIR}/build/"
|
||||
echo ""
|
||||
echo "Library: ${SCRIPT_DIR}/build/libmath.so"
|
||||
echo ""
|
||||
echo "To test: cd ${SCRIPT_DIR}/test_client && make && LD_LIBRARY_PATH=../build ./test_client"
|
||||
@@ -0,0 +1,16 @@
|
||||
# Generated by Clojure/Nim
|
||||
import cljnim_runtime
|
||||
|
||||
proc square*(x: CljVal): CljVal =
|
||||
cljMul(@[x, x])
|
||||
proc cube*(x: CljVal): CljVal =
|
||||
cljMul(@[x, x, x])
|
||||
proc add*(a: CljVal, b: CljVal): CljVal =
|
||||
cljAdd(@[a, b])
|
||||
proc greet*(name: CljVal): CljVal =
|
||||
cljStrConcat(@[cljString("Hello, "), name, cljString("!")])
|
||||
proc factorial*(n: CljVal): CljVal =
|
||||
if cljIsTruthy(cljNumEq(@[n, cljInt(0)])):
|
||||
result = cljInt(1)
|
||||
else:
|
||||
result = cljMul(@[n, factorial(cljSub(@[n, cljInt(1)]))])
|
||||
@@ -0,0 +1,18 @@
|
||||
(ns math)
|
||||
|
||||
(defn square [x]
|
||||
(* x x))
|
||||
|
||||
(defn cube [x]
|
||||
(* x x x))
|
||||
|
||||
(defn add [a b]
|
||||
(+ a b))
|
||||
|
||||
(defn greet [name]
|
||||
(str "Hello, " name "!"))
|
||||
|
||||
(defn factorial [n]
|
||||
(if (= n 0)
|
||||
1
|
||||
(* n (factorial (- n 1)))))
|
||||
@@ -0,0 +1,23 @@
|
||||
# C-friendly wrappers around Clojure-generated code
|
||||
# Exports plain C types (int, char*) instead of CljVal
|
||||
|
||||
import cljnim_runtime
|
||||
|
||||
# Import the generated Clojure functions
|
||||
include build/math
|
||||
|
||||
proc c_square*(x: cint): cint {.exportc, dynlib.} =
|
||||
let r = square(cljInt(x.int64))
|
||||
return r.intVal.cint
|
||||
|
||||
proc c_factorial*(n: cint): cint {.exportc, dynlib.} =
|
||||
let r = factorial(cljInt(n.int64))
|
||||
return r.intVal.cint
|
||||
|
||||
proc c_add*(a, b: cint): cint {.exportc, dynlib.} =
|
||||
let r = add(cljInt(a.int64), cljInt(b.int64))
|
||||
return r.intVal.cint
|
||||
|
||||
proc c_greet*(name: cstring): cstring {.exportc, dynlib.} =
|
||||
let r = greet(cljString($name))
|
||||
return r.strVal.cstring
|
||||
@@ -0,0 +1,11 @@
|
||||
CC = gcc
|
||||
CFLAGS = -I../build -L../build
|
||||
LDFLAGS = -lmath -Wl,-rpath,'$$ORIGIN/../build'
|
||||
|
||||
all: test_client
|
||||
|
||||
test_client: main.c
|
||||
$(CC) $(CFLAGS) main.c $(LDFLAGS) -o test_client
|
||||
|
||||
clean:
|
||||
rm -f test_client
|
||||
@@ -0,0 +1,24 @@
|
||||
#include <stdio.h>
|
||||
|
||||
// Declarations from the shared library
|
||||
extern int c_square(int x);
|
||||
extern int c_factorial(int n);
|
||||
extern int c_add(int a, int b);
|
||||
extern const char* c_greet(const char* name);
|
||||
|
||||
// Nim runtime init
|
||||
extern void NimMain(void);
|
||||
|
||||
int main(void) {
|
||||
NimMain();
|
||||
|
||||
printf("=== Clojure/Nim Native Library Test ===\n\n");
|
||||
|
||||
printf("c_square(7) = %d\n", c_square(7));
|
||||
printf("c_add(10, 20) = %d\n", c_add(10, 20));
|
||||
printf("c_factorial(5) = %d\n", c_factorial(5));
|
||||
printf("c_greet(\"World\") = %s\n", c_greet("World"));
|
||||
|
||||
printf("\nAll tests passed! Clojure in a .so file.\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Clojure/Nim → WASM (via Emscripten)
|
||||
|
||||
Compile Clojure code to WebAssembly using Nim's Emscripten backend.
|
||||
|
||||
## Why?
|
||||
|
||||
- **Native speed in browser** — no JS interpreter overhead
|
||||
- **Smaller than JVM** — WASM module is ~100KB vs 50MB+ JVM
|
||||
- **Secure sandbox** — WASM runs in browser's security model
|
||||
|
||||
## Requirements
|
||||
|
||||
```bash
|
||||
git clone https://github.com/emscripten-core/emsdk.git
|
||||
cd emsdk
|
||||
./emsdk install latest
|
||||
./emsdk activate latest
|
||||
source ./emsdk_env.sh
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Then open `www/index.html` in a browser.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `cljnim compile-lib` — generates Nim with exported functions
|
||||
2. `nim c -d:emscripten` — compiles Nim to WASM + JS glue
|
||||
3. Browser loads `math.js` (glue) + `math.wasm` (WASM module)
|
||||
4. JS calls `_wasmSquare(7)` → WASM executes native Clojure code
|
||||
|
||||
## Future
|
||||
|
||||
- WASI target (server-side WASM)
|
||||
- wasm32-wasi-musl via Zig (no Emscripten needed)
|
||||
- DOM interop through Emscripten bindings
|
||||
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLJNIM="${SCRIPT_DIR}/../../cljnim"
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib"
|
||||
|
||||
mkdir -p "${SCRIPT_DIR}/build"
|
||||
|
||||
echo "=== Step 1: Clojure → Nim ==="
|
||||
"$CLJNIM" compile-lib \
|
||||
"${SCRIPT_DIR}/examples/math.clj" \
|
||||
"${SCRIPT_DIR}/build/math.nim"
|
||||
|
||||
echo "=== Step 2: Nim → WASM (via Emscripten) ==="
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
if ! command -v emcc &> /dev/null; then
|
||||
echo "ERROR: Emscripten not found."
|
||||
echo "Install: git clone https://github.com/emscripten-core/emsdk.git"
|
||||
echo " ./emsdk install latest && ./emsdk activate latest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
emcc nim c --cc:clang \
|
||||
--path:"$LIB_PATH" \
|
||||
-d:emscripten -d:release \
|
||||
--noMain \
|
||||
-o:"build/math.js" \
|
||||
"wasm_wrappers.nim"
|
||||
|
||||
echo "=== Step 3: Generated files ==="
|
||||
ls -la "${SCRIPT_DIR}/build/"
|
||||
echo ""
|
||||
echo "Open ${SCRIPT_DIR}/www/index.html in a browser to test"
|
||||
@@ -0,0 +1,9 @@
|
||||
(ns math)
|
||||
|
||||
(defn square [x]
|
||||
(* x x))
|
||||
|
||||
(defn factorial [n]
|
||||
(if (= n 0)
|
||||
1
|
||||
(* n (factorial (- n 1)))))
|
||||
@@ -0,0 +1,14 @@
|
||||
# WASM-friendly wrappers around Clojure-generated code
|
||||
# Uses Emscripten FFI: cint → JS number, cstring → JS string
|
||||
|
||||
import cljnim_runtime
|
||||
|
||||
include build/math
|
||||
|
||||
proc wasmSquare*(x: cint): cint {.exportc.} =
|
||||
let r = square(cljInt(x.int64))
|
||||
return r.intVal.cint
|
||||
|
||||
proc wasmFactorial*(n: cint): cint {.exportc.} =
|
||||
let r = factorial(cljInt(n.int64))
|
||||
return r.intVal.cint
|
||||
@@ -0,0 +1,35 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Clojure/Nim → WASM Demo</title>
|
||||
<style>
|
||||
body { font-family: monospace; max-width: 800px; margin: 40px auto; padding: 20px; background: #1a1a2e; color: #eee; }
|
||||
h1 { color: #e94560; }
|
||||
.result { background: #16213e; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>⚡ Clojure/Nim → WASM (Emscripten)</h1>
|
||||
<p>Clojure compiled to Nim, then to WebAssembly via Emscripten.</p>
|
||||
|
||||
<div class="result">
|
||||
<div id="output">Loading WASM module...</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var Module = {
|
||||
onRuntimeInitialized: function() {
|
||||
const out = document.getElementById('output');
|
||||
out.innerHTML = `
|
||||
<b>wasmSquare(7)</b> = ${Module._wasmSquare(7)}<br>
|
||||
<b>wasmFactorial(5)</b> = ${Module._wasmFactorial(5)}<br>
|
||||
<br>
|
||||
<i>Clojure running at native speed in your browser!</i>
|
||||
`;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script src="../build/math.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
exec /home/ziko/lispnim/experiments/wasm/tools/zig/zig cc "$@"
|
||||
@@ -0,0 +1,36 @@
|
||||
# Clojure/Nim → JavaScript
|
||||
|
||||
Compile Clojure code to JavaScript via Nim's JS backend.
|
||||
|
||||
## Why?
|
||||
|
||||
- **No JVM** — Clojure in the browser without 50MB runtime
|
||||
- **No JavaScript** — write Clojure, get JS
|
||||
- **Smaller than ClojureScript** — no Google Closure compiler needed
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
./build.sh
|
||||
```
|
||||
|
||||
Then open `www/index.html` in a browser.
|
||||
|
||||
## How it works
|
||||
|
||||
1. `cljnim compile-lib` — generates Nim with exported functions
|
||||
2. `js_wrappers.nim` — thin wrappers that convert CljVal ↔ JS types
|
||||
3. `nim js` — compiles Nim to JavaScript
|
||||
4. Browser loads `math.js` and calls `jsSquare(7)`, `jsFactorial(5)`, etc.
|
||||
|
||||
## Limitations
|
||||
|
||||
- Full `cljnim_runtime.nim` uses C FFI (threads, processes) — not JS-compatible
|
||||
- For now, only numeric and string functions work
|
||||
- Persistent data structures need JS-specific runtime
|
||||
|
||||
## Future
|
||||
|
||||
- Full JS runtime for HAMT vectors/maps
|
||||
- DOM interop: `(dom/getElementById "app")`
|
||||
- npm package output
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLJNIM="${SCRIPT_DIR}/../../cljnim"
|
||||
LIB_PATH="${SCRIPT_DIR}/../../lib"
|
||||
|
||||
mkdir -p "${SCRIPT_DIR}/build"
|
||||
|
||||
echo "=== Step 1: Clojure → Nim ==="
|
||||
"$CLJNIM" compile-lib \
|
||||
"${SCRIPT_DIR}/examples/math.clj" \
|
||||
"${SCRIPT_DIR}/build/math.nim"
|
||||
|
||||
echo "=== Step 2: Patch runtime for JS ==="
|
||||
sed -i 's/import cljnim_runtime/import cljnim_runtime_js/' "${SCRIPT_DIR}/build/math.nim"
|
||||
|
||||
echo "=== Step 3: Nim → JavaScript ==="
|
||||
cd "$SCRIPT_DIR" && nim js -d:release \
|
||||
--path:"$LIB_PATH" \
|
||||
--path:"build" \
|
||||
-o:"build/math.js" \
|
||||
"js_wrappers.nim"
|
||||
|
||||
echo "=== Step 3: Generated files ==="
|
||||
ls -la "${SCRIPT_DIR}/build/"
|
||||
echo ""
|
||||
echo "JS: ${SCRIPT_DIR}/build/math.js"
|
||||
echo ""
|
||||
echo "Open ${SCRIPT_DIR}/www/index.html in a browser to test"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
# Generated by Clojure/Nim
|
||||
import cljnim_runtime_js
|
||||
|
||||
proc square*(x: CljVal): CljVal =
|
||||
cljMul(@[x, x])
|
||||
proc factorial*(n: CljVal): CljVal =
|
||||
if cljIsTruthy(cljNumEq(@[n, cljInt(0)])):
|
||||
result = cljInt(1)
|
||||
else:
|
||||
result = cljMul(@[n, factorial(cljSub(@[n, cljInt(1)]))])
|
||||
proc greet*(name: CljVal): CljVal =
|
||||
cljStrConcat(@[cljString("Hello, "), name, cljString("!")])
|
||||
@@ -0,0 +1,12 @@
|
||||
(ns math)
|
||||
|
||||
(defn square [x]
|
||||
(* x x))
|
||||
|
||||
(defn factorial [n]
|
||||
(if (= n 0)
|
||||
1
|
||||
(* n (factorial (- n 1)))))
|
||||
|
||||
(defn greet [name]
|
||||
(str "Hello, " name "!"))
|
||||
@@ -0,0 +1,19 @@
|
||||
# JS-friendly wrappers around Clojure-generated code
|
||||
# These expose plain JS types (number, string) instead of CljVal
|
||||
|
||||
import cljnim_runtime_js
|
||||
|
||||
# Import the generated Clojure functions
|
||||
include build/math
|
||||
|
||||
proc jsSquare*(x: int): int {.exportc.} =
|
||||
let r = square(cljInt(x.int64))
|
||||
return r.intVal.int
|
||||
|
||||
proc jsFactorial*(n: int): int {.exportc.} =
|
||||
let r = factorial(cljInt(n.int64))
|
||||
return r.intVal.int
|
||||
|
||||
proc jsGreet*(name: cstring): cstring {.exportc.} =
|
||||
let r = greet(cljString($name))
|
||||
return r.strVal.cstring
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Clojure/Nim → JS Demo</title>
|
||||
<style>
|
||||
body { font-family: monospace; max-width: 800px; margin: 40px auto; padding: 20px; background: #1a1a2e; color: #eee; }
|
||||
h1 { color: #e94560; }
|
||||
.result { background: #16213e; padding: 15px; margin: 10px 0; border-radius: 5px; }
|
||||
.label { color: #0f3460; font-weight: bold; }
|
||||
button { background: #e94560; color: white; border: none; padding: 10px 20px; cursor: pointer; border-radius: 3px; }
|
||||
button:hover { background: #ff6b6b; }
|
||||
input { padding: 8px; font-size: 16px; border-radius: 3px; border: 1px solid #0f3460; background: #16213e; color: white; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔥 Clojure/Nim → JavaScript</h1>
|
||||
<p>Clojure code compiled to Nim, then to JavaScript. Running in your browser.</p>
|
||||
|
||||
<div class="result">
|
||||
<div class="label">square(7)</div>
|
||||
<div id="square-result">Click button to calculate</div>
|
||||
<br>
|
||||
<button onclick="document.getElementById('square-result').textContent = jsSquare(7)">Calculate</button>
|
||||
</div>
|
||||
|
||||
<div class="result">
|
||||
<div class="label">factorial(5)</div>
|
||||
<div id="fact-result">Click button to calculate</div>
|
||||
<br>
|
||||
<button onclick="document.getElementById('fact-result').textContent = jsFactorial(5)">Calculate</button>
|
||||
</div>
|
||||
|
||||
<div class="result">
|
||||
<div class="label">greet("World")</div>
|
||||
<div id="greet-result">Click button to greet</div>
|
||||
<br>
|
||||
<button onclick="document.getElementById('greet-result').textContent = jsGreet('World')">Greet</button>
|
||||
</div>
|
||||
|
||||
<div class="result">
|
||||
<div class="label">Interactive: factorial of</div>
|
||||
<input type="number" id="n-input" value="10" min="0" max="20">
|
||||
<button onclick="document.getElementById('interactive-result').textContent = jsFactorial(parseInt(document.getElementById('n-input').value))">Compute</button>
|
||||
<div id="interactive-result" style="margin-top:10px;"></div>
|
||||
</div>
|
||||
|
||||
<script src="../build/math.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user