Add AI-first REPL with JSON mode
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
# AI-First Clojure/Nim
|
||||
|
||||
## Философия
|
||||
|
||||
Този проект не е за човешки програмисти с Emacs/VS Code. Този проект е за **AI агенти**, които работят през терминал.
|
||||
|
||||
### Защо AI-First?
|
||||
|
||||
- AI не може да клика в GUI. AI работи с текст, команди и структурирани данни.
|
||||
- AI не се нуждае от syntax highlighting. AI се нуждае от **структуриран изход**.
|
||||
- AI не иска REPL с цветни prompt-ове. AI иска **EDN/JSON отговори** с метаданни.
|
||||
- AI commit-ва кода автоматично. Нуждаем се от **git-aware REPL**.
|
||||
|
||||
## Архитектура на AI REPL
|
||||
|
||||
```
|
||||
┌─────────────┐ EDN/JSON ┌─────────────────┐
|
||||
│ AI Agent │ ◄────────────────► │ cljnim repl │
|
||||
│ (терминал) │ structured I/O │ (structured) │
|
||||
└─────────────┘ └────────┬────────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ Compiler │
|
||||
│ + Eval │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ Git Ops │
|
||||
│ (вградени) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
## Принципи
|
||||
|
||||
### 1. Структуриран I/O
|
||||
|
||||
Всеки вход и изход е структуриран:
|
||||
|
||||
```edn
|
||||
;; AI изпраща:
|
||||
{:op :eval
|
||||
:form "(+ 1 2 3)"
|
||||
:ns "user"
|
||||
:request-id "uuid-1234"}
|
||||
|
||||
;; REPL връща:
|
||||
{:status :ok
|
||||
:request-id "uuid-1234"
|
||||
:result {:type :int
|
||||
:value 6
|
||||
:printed "6"}
|
||||
:meta {:ms 0.003
|
||||
:form "(+ 1 2 3)"
|
||||
:ns "user"}}
|
||||
|
||||
;; Грешка:
|
||||
{:status :error
|
||||
:request-id "uuid-1234"
|
||||
:error {:type :compiler/unknown-symbol
|
||||
:symbol "unknwon-fn"
|
||||
:line 1
|
||||
:column 5
|
||||
:message "Unknown symbol: unknwon-fn"
|
||||
:trace []}}
|
||||
```
|
||||
|
||||
### 2. Batch Evaluation
|
||||
|
||||
AI може да изпрати множество форми наведнъж:
|
||||
|
||||
```edn
|
||||
{:op :eval-batch
|
||||
:forms ["(defn add [a b] (+ a b))"
|
||||
"(add 10 20)"
|
||||
"(def data {:x 1 :y 2})"]
|
||||
:mode :compile}
|
||||
```
|
||||
|
||||
### 3. Git-Aware REPL
|
||||
|
||||
Вградени git операции без напускане на REPL:
|
||||
|
||||
```clojure
|
||||
;; Clojure форми, които управляват git:
|
||||
(git/status)
|
||||
;; => {:branch "main" :untracked ["src/new.clj"] :modified []}
|
||||
|
||||
(git/diff)
|
||||
;; => {:files [{:path "src/core.clj" :added 5 :removed 2}]}
|
||||
|
||||
(git/commit "Add factorial" {:push true})
|
||||
;; => {:sha "a1b2c3d" :pushed true}
|
||||
```
|
||||
|
||||
### 4. Self-Describing Система
|
||||
|
||||
AI винаги може да попита системата какво може:
|
||||
|
||||
```clojure
|
||||
(apropos "map")
|
||||
;; => [{:name "map" :ns "clojure.core" :type :function}
|
||||
;; {:name "map?" :ns "clojure.core" :type :function}
|
||||
;; {:name "mapv" :ns "clojure.core" :type :function}]
|
||||
|
||||
(doc map)
|
||||
;; => {:name "map"
|
||||
;; :ns "clojure.core"
|
||||
;; :doc "Returns a lazy sequence..."
|
||||
;; :args [["f" "coll"] ["f" "c1" "c2" "& colls"]]
|
||||
;; :source "(defn map [f coll] ...)"}
|
||||
```
|
||||
|
||||
### 5. File Operations през REPL
|
||||
|
||||
AI не отваря редактор. AI манипулира файлове през Clojure:
|
||||
|
||||
```clojure
|
||||
(file/read "src/core.clj")
|
||||
;; => "(ns core)..."
|
||||
|
||||
(file/write "src/new.clj" "(ns new)")
|
||||
;; => {:bytes-written 8 :path "src/new.clj"}
|
||||
|
||||
(file/ls "src")
|
||||
;; => ["types.nim" "reader.nim" "emitter.nim"]
|
||||
```
|
||||
|
||||
### 6. Tool-Call Формат
|
||||
|
||||
REPL може да работи в `tool-call` режим за директна интеграция с AI frameworks:
|
||||
|
||||
```json
|
||||
{
|
||||
"tool": "cljnim/eval",
|
||||
"arguments": {
|
||||
"code": "(defn fib [n] (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))",
|
||||
"mode": "compile"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Режими на REPL
|
||||
|
||||
### Човешки режим (default)
|
||||
```
|
||||
cljnim> (+ 1 2)
|
||||
3
|
||||
cljnim> (defn square [x] (* x x))
|
||||
#'user/square
|
||||
```
|
||||
|
||||
### AI режим (`--ai` или `--json`)
|
||||
```bash
|
||||
$ cljnim repl --json
|
||||
{"status":"ready","ns":"user"}
|
||||
> (defn add [a b] (+ a b))
|
||||
{"status":"ok","result":{"type":"var","name":"add","ns":"user"}}
|
||||
> (add 10 20)
|
||||
{"status":"ok","result":{"type":"int","value":30,"printed":"30"},"meta":{"ms":0.001}}
|
||||
```
|
||||
|
||||
### Batch режим
|
||||
```bash
|
||||
$ cljnim run-batch script.clj --format json
|
||||
[{"form":"(defn f [x] x)","status":"ok","result":"#'user/f"}
|
||||
{"form":"(f 42)","status":"ok","result":{"type":"int","value":42}}]
|
||||
```
|
||||
|
||||
## Интеграция с AI Workflow
|
||||
|
||||
```bash
|
||||
# 1. AI клонира репото
|
||||
git clone git@gitlab.com:balvatar/lisp-nim.git
|
||||
cd lisp-nim
|
||||
|
||||
# 2. AI стартира REPL в JSON режим
|
||||
cljnim repl --json
|
||||
|
||||
# 3. AI изпраща форми, получава структурирани отговори
|
||||
# 4. AI пише файлове през (file/write ...)
|
||||
# 5. AI commit-ва през (git/commit ...)
|
||||
# 6. AI push-ва през (git/push)
|
||||
```
|
||||
|
||||
## Защо това е бъдещето?
|
||||
|
||||
| Аспект | Човешки IDE | AI Terminal |
|
||||
|---|---|---|
|
||||
| Интерфейс | GUI + мишка | Текст + команды |
|
||||
| Навигация | Кликване | `(apropos ...)`, `(doc ...)` |
|
||||
| Рефакториране | Ръчно | Batch eval + git diff |
|
||||
| Тестване | Run button | `(run-tests)` структуриран отговор |
|
||||
| Commit | GUI диалог | `(git/commit "msg")` |
|
||||
| Context | Отворени файлове | REPL ns + `:context` |
|
||||
+77
-338
@@ -1,369 +1,108 @@
|
||||
# Пътна Карта: Clojure/Nim — Да Бием JVM Clojure
|
||||
# Пътна Карта: Clojure/Nim (AI-First Edition)
|
||||
|
||||
> **Стратегия:** Не се опитваме да копираме JVM Clojure. Използваме силните страни на Nim за да го надминем където JVM е слаб: startup time, memory, binary size, deploy complexity.
|
||||
## Фаза 0: Компилаторно Ядро ✅
|
||||
|
||||
## Убийствени Предимства (Killer Metrics)
|
||||
- [x] Clojure Reader (EDN парсер)
|
||||
- [x] AST → Nim Emitter
|
||||
- [x] CLI (`compile`, `run`, `read`)
|
||||
- [x] Специални форми: `def`, `defn`, `fn`, `let`, `if`, `do`, `quote`
|
||||
- [x] Оператори: `+`, `-`, `*`, `/`, `=`, `<`, `>`
|
||||
- [x] Функции: `println` → `echo`
|
||||
- [x] Примери: `hello.clj`, `math.clj` (факториел)
|
||||
|
||||
**Реални бенчмаркове (AOT compiled vs JVM Clojure):**
|
||||
## Фаза 1: AI-First REPL 🔄
|
||||
|
||||
| Метрика | JVM Clojure | Clojure/Nim | Коефициент |
|
||||
|---|---|---|---|
|
||||
| Hello World startup | 543ms | 1ms | **543x по-бърз** |
|
||||
| Fibonacci 30 (recursive) | 715ms | 24ms | **29x по-бърз** |
|
||||
| Factorial 20 (recursive) | 679ms | 1ms | **679x по-бърз** |
|
||||
| Binary size | 4.1GB (JVM+Clojure) | 92KB | **45,000x по-малко** |
|
||||
**Цел:** REPL, който AI може да ползва през терминал без редактор.
|
||||
|
||||
---
|
||||
### 1.1 Структуриран REPL
|
||||
- [ ] JSON/EDN режим (`cljnim repl --json`)
|
||||
- [ ] Batch evaluation (`cljnim run-batch script.clj --format json`)
|
||||
- [ ] Структурирани грешки (line, column, type, message, trace)
|
||||
- [ ] Метаданни в отговора (ms, ns, form, type)
|
||||
|
||||
## Фаза 0: Инфраструктура ✅
|
||||
### 1.2 Self-Describing Система
|
||||
- [ ] `(doc fn)` — връща структурирани метаданни
|
||||
- [ ] `(apropos "str")` — търсене на функции
|
||||
- [ ] `(source fn)` — изходен код на функция
|
||||
- [ ] `(ns-publics 'clojure.core)` — листване на публични символи
|
||||
|
||||
- [x] Определяне на цели и архитектура
|
||||
- [x] Избор на подход (AOT компилатор)
|
||||
- [x] Създаване на структура на проекта
|
||||
- [x] CI/CD pipeline (GitLab CI)
|
||||
- [x] Тестов framework (unittest)
|
||||
- [x] **Benchmarks suite** — сравняване с JVM Clojure
|
||||
### 1.3 File Operations
|
||||
- [ ] `(file/read "path")` — четене на файл
|
||||
- [ ] `(file/write "path" "content")` — запис на файл
|
||||
- [ ] `(file/append "path" "content")` — добавяне
|
||||
- [ ] `(file/ls "dir")` — листване на директория
|
||||
- [ ] `(file/exists? "path")` — проверка
|
||||
|
||||
## Фаза 1: Reader + Базов Emitter ✅
|
||||
### 1.4 Git Operations (вградени)
|
||||
- [ ] `(git/status)` — текущ статус
|
||||
- [ ] `(git/diff)` — разлики
|
||||
- [ ] `(git/add "file")` — stage
|
||||
- [ ] `(git/commit "msg")` — commit
|
||||
- [ ] `(git/push)` — push
|
||||
- [ ] `(git/log)` — история
|
||||
|
||||
**Цел:** `hello.clj` → native binary за < 1 секунда. **Постигнато: 1ms AOT, 543x по-бърз от JVM!**
|
||||
## Фаза 2: Persistent Структури от Данни
|
||||
|
||||
### Reader
|
||||
- [x] Числа (int, float)
|
||||
- [x] Низове с escape sequences
|
||||
- [x] Символи и keywords
|
||||
- [x] Коментари (`;`)
|
||||
- [x] Списъци `(...)` и вектори `[...]`
|
||||
- [x] Maps `{...}` и Sets `#{...}`
|
||||
- [x] Цитиране (`'`, `` ` ``, `~`, `~@`)
|
||||
- [x] Keyword namespaces (`:ns/key`)
|
||||
- [ ] Reader conditionals (`#?()`)
|
||||
- [x] Метаданни (`^`)
|
||||
- [x] Dispatch macros (`#'`, `#_`)
|
||||
**Цел:** Истински Clojure immutable структури.
|
||||
|
||||
### Emitter (минимален)
|
||||
- [x] `println`, `def`, `defn`, `fn`, `let`, `if`, `do`
|
||||
- [x] `when`, `cond`, `loop`/`recur`
|
||||
- [x] `try`/`catch`/`finally`
|
||||
- [x] Map литерали в emitter
|
||||
- [x] `str`, `not`, `not=`, `>=`, `<=`
|
||||
- [ ] **Persistent Vector** (Hash Array Mapped Trie)
|
||||
- [ ] **Persistent Map** (HAMT)
|
||||
- [ ] **Persistent Set**
|
||||
- [ ] `conj`, `assoc`, `dissoc`, `get`, `get-in`
|
||||
- [ ] `nth`, `first`, `rest`, `last`, `count`
|
||||
|
||||
### Milestone: Hello World Benchmark ✅
|
||||
```bash
|
||||
# РЕЗУЛТАТ: 543x по-бърз от JVM Clojure!
|
||||
time cljnim run hello.clj # ~290ms (with Nim compile)
|
||||
time cljnim compile hello.clj && time ./hello # ~1ms (AOT)
|
||||
time clojure hello.clj # ~543ms
|
||||
```
|
||||
## Фаза 3: Clojure Core Библиотека
|
||||
|
||||
## Фаза 2: Runtime + Core Функции ✅
|
||||
**Цел:** Достатъчно функции за реални скриптове.
|
||||
|
||||
**Цел:** Да работи достатъчно Clojure код за реални скриптове. **Постигнато: `(map square [1 2 3 4 5])` → `(1 4 9 16 25)`!**
|
||||
### 3.1 Seq функции
|
||||
- [ ] `map`, `filter`, `reduce`
|
||||
- [ ] `take`, `drop`, `partition`
|
||||
- [ ] `concat`, `interleave`
|
||||
- [ ] `range`
|
||||
|
||||
### Persistent Data Structures
|
||||
- [x] **CljVector** — seq-based с copy-on-write семантика
|
||||
- [x] **CljMap** — seq-based key/value с copy-on-write
|
||||
- [ ] **HAMT** (Hash Array Mapped Trie) — за O(log₃₂ n) операции
|
||||
### 3.2 Низове и IO
|
||||
- [ ] `str`, `pr-str`, `println`, `prn`
|
||||
- [ ] `slurp`, `spit`
|
||||
- [ ] `read-line`
|
||||
|
||||
### Core функции — Collections
|
||||
- [x] `map`, `filter`, `reduce`, `mapv`
|
||||
- [x] `first`, `rest`, `next`, `last`, `nth`
|
||||
- [x] `conj`, `into`, `cons`
|
||||
- [x] `assoc`, `dissoc`, `get`, `get-in`, `update-in`
|
||||
- [x] `keys`, `vals`, `select-keys`
|
||||
- [x] `count`, `empty?`, `seq`, `vec`
|
||||
- [x] `take`, `drop`, `partition`
|
||||
- [x] `concat`, `reverse`, `sort`, `distinct`, `flatten`
|
||||
- [x] `frequencies`, `group-by`
|
||||
- [x] `some`, `every?`
|
||||
### 3.3 Мета
|
||||
- [ ] `meta`, `with-meta`, `vary-meta`
|
||||
- [ ] `type`, `instance?`
|
||||
|
||||
### Core функции — Strings
|
||||
- [x] `str`, `pr-str`, `println`, `prn`
|
||||
- [x] `subs`, `clojure.string/join`, `clojure.string/split`
|
||||
- [x] `clojure.string/replace`, `clojure.string/trim`
|
||||
- [x] `starts-with?`, `ends-with?`, `includes?`
|
||||
- [x] `upper-case`, `lower-case`
|
||||
## Фаза 4: Макро Система
|
||||
|
||||
### Core функции — Numbers & Logic
|
||||
- [x] `inc`, `dec`, `zero?`, `pos?`, `neg?`, `even?`, `odd?`
|
||||
- [x] `min`, `max`, `abs`, `mod`
|
||||
- [x] `and`, `or`, `not`, `not=`
|
||||
- [x] `apply`, `comp`, `partial`, `juxt`, `complement`, `constantly`
|
||||
**Цел:** `defmacro` да работи.
|
||||
|
||||
### Core функции — IO & Misc
|
||||
- [x] `identity`, `type`
|
||||
- [x] `atom`, `deref`, `reset!`, `swap!`
|
||||
- [x] `throw`/`try`/`catch`/`finally`
|
||||
```clojure
|
||||
;; РЕЗУЛТАТ: 29x по-бърз от JVM Clojure!
|
||||
(defn fib [n]
|
||||
(if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
|
||||
- [ ] `macroexpand`, `macroexpand-1`
|
||||
- [ ] `syntax-quote`, `unquote`, `unquote-splicing`
|
||||
- [ ] `gensym`
|
||||
- [ ] Core макроси: `->`, `->>`, `and`, `or`, `when`, `cond`
|
||||
|
||||
(println (fib 30))
|
||||
```
|
||||
```bash
|
||||
time cljnim run fib.clj # ~290ms (with Nim compile)
|
||||
time cljnim compile fib.clj && time ./fib # ~24ms (AOT)
|
||||
time clojure fib.clj # ~715ms
|
||||
```
|
||||
## Фаза 5: Nim Interop
|
||||
|
||||
## Фаза 3: Макро Система ✅
|
||||
**Цел:** Достъп до целия Nim ecosystem.
|
||||
|
||||
**Цел:** Clojure макроси, които работят. **Постигнато: `defmacro`, `->`, `->>`, `and`, `or`, `when`, `unless` — всичко работи!**
|
||||
- [ ] `(nim/import "strutils")` — import на Nim модул
|
||||
- [ ] `(nim/ffi "math.h" :functions [["sin" :double [:double]]])` — C FFI
|
||||
- [ ] `(nim/call "strutils.join" ", " ["a" "b"])` — извикване
|
||||
|
||||
### Специални форми
|
||||
- [x] `def`, `fn`/`defn`, `let`/`letfn`
|
||||
- [x] `if`/`when`/`cond`
|
||||
- [x] `do`, `quote`, `var`, `set!`
|
||||
- [x] `loop`/`recur`
|
||||
- [x] `throw`/`try`/`catch`/`finally`
|
||||
## Фаза 6: AOT Компилация + REPL
|
||||
|
||||
### Макро engine
|
||||
- [x] `defmacro` — дефиниране на макроси с compile-time evaluator
|
||||
- [x] Syntax-quote, unquote, unquote-splicing
|
||||
- [x] `gensym`
|
||||
- [x] Mini-evaluator: `list`, `cons`, `concat`, `vec`, `conj`, `str`, `quote`
|
||||
**Цел:** Бързо стартиране на скриптове.
|
||||
|
||||
### Core макроси
|
||||
- [x] `->`, `->>`, `as->`, `some->`, `some->>`
|
||||
- [x] `cond`, `cond->`, `cond->>`
|
||||
- [x] `and`, `or`, `when-let`, `if-let`
|
||||
- [x] `when`, `when-not`, `doto`, `with-open`
|
||||
- [x] `for`, `doseq`, `dotimes`
|
||||
- [x] `comment`, `assert`, `defn-`
|
||||
- [ ] AOT компилация на цели проекти
|
||||
- [ ] Кеширане на компилирани модули
|
||||
- [ ] REPL в self-hosted режим (като ClojureScript)
|
||||
|
||||
### Milestone: Макро Бенчмарк ✅
|
||||
```clojure
|
||||
;; Работи без промяна от JVM Clojure!
|
||||
(defmacro unless [condition & body]
|
||||
(list 'if condition nil (cons 'do body)))
|
||||
## Фаза 7: Многонишковост
|
||||
|
||||
(unless false (println "Macros work!")) ; => "Macros work!"
|
||||
```
|
||||
|
||||
## Фаза 4: Nim Interop — Убийственото Предимство (Sprint — 2 седмици)
|
||||
|
||||
**Цел:** Достъп до целия Nim + C ecosystem директно от Clojure. Това е нещо, което JVM Clojure **не може** да направи.
|
||||
|
||||
### Nim Interop
|
||||
```clojure
|
||||
;; Достъп до Nim стандартната библиотека — РАБОТИ!
|
||||
(nim/math/sin 1.5708) ; => 0.9999999999932537
|
||||
(nim/math/sqrt 144.0) ; => 12.0
|
||||
(nim/strutils/toUpper "clojure") ; => "CLOJURE"
|
||||
(nim/strutils/endsWith? "hello" "lo") ; => true
|
||||
```
|
||||
|
||||
### Interop features
|
||||
- [x] `nim/` namespace за Nim стандартната библиотека
|
||||
- [x] Auto-import на използвани Nim модули
|
||||
- [x] Type mapping: Clojure ↔ Nim (int, float, string, bool)
|
||||
- [x] CLI: `cljnim -e '(println (+ 1 2 3))'`
|
||||
- [ ] FFI импорт на C функции (headers + dynamic libraries)
|
||||
- [ ] Nim object access (fields, methods)
|
||||
- [ ] Callback passing (Clojure fn → Nim proc → C function pointer)
|
||||
|
||||
### Milestone: Nim Interop ✅
|
||||
```clojure
|
||||
;; JVM Clojure: не може директно да вика C функции
|
||||
;; Clojure/Nim: директен достъп до Nim + C ecosystem
|
||||
(nim/math/pow 2.0 10.0) ; => 1024.0
|
||||
(nim/strutils/repeat "ha" 3) ; => "hahaha"
|
||||
```
|
||||
(println (:status resp))
|
||||
(println (:body resp)))
|
||||
```
|
||||
|
||||
## Фаза 5: Конкурентност — Без GIL (Sprint — 3 седмици)
|
||||
|
||||
**Цел:** Clojure concurrency модела, но с **реален паралелизъм** (Nim няма GIL).
|
||||
|
||||
### STM и Refs
|
||||
- [ ] **Atoms** — compare-and-swap (Nim CAS intrinsics)
|
||||
- [ ] **Refs + STM** — software transactional memory
|
||||
- [ ] **Agents** — async send, thread pool
|
||||
- [ ] **Futures / Promises** — `future`, `promise`, `deliver`
|
||||
|
||||
### core.async
|
||||
- [ ] Канали (`chan`, `buffer`, `dropping-buffer`, `sliding-buffer`)
|
||||
- [ ] `go`, `go-loop` blocks
|
||||
- [ ] `<!`, `>!`, `<!!`, `>!!`
|
||||
- [ ] `alts!`, `alts!!`
|
||||
- [ ] `timeout`, `close!`
|
||||
|
||||
### Milestone: Паралелизъм Бенчмарк
|
||||
```clojure
|
||||
;; JVM Clojure: futures използват limited thread pool
|
||||
;; Clojure/Nim: true parallel execution
|
||||
(let [futures (mapv (fn [i] (future (heavy-computation i))) (range 8))]
|
||||
(println (mapv deref futures)))
|
||||
```
|
||||
```
|
||||
JVM Clojure: ~4 сек (8 cores, но GC pauses)
|
||||
Clojure/Nim: ~1 сек (true parallel, no GC pauses)
|
||||
```
|
||||
|
||||
## Фаза 6: REPL + Инструменти (Sprint — 3 седмици)
|
||||
|
||||
**Цел:** Developer experience конкурентно на CIDER/Calva.
|
||||
|
||||
### REPL
|
||||
- [ ] **REPL цикъл** (Read-Eval-Print-Loop)
|
||||
- [ ] **Incremental compilation** (компилира само променените forms)
|
||||
- [ ] **Hot reloading** на namespace
|
||||
- [ ] **Error reporting** с Clojure-style stack traces
|
||||
- [ ] **Syntax highlighting** в терминала
|
||||
|
||||
### Инструменти
|
||||
- [ ] **nREPL server** — съвместим с CIDER, Calva, IntelliJ Cursive
|
||||
- [ ] **LSP server** — autocomplete, go-to-definition, hover docs
|
||||
- [ ] `doc`, `source`, `apropos` функции
|
||||
- [ ] **AOT компилация** — `cljnim build` → single binary
|
||||
- [ ] **Watch mode** — `cljnim watch` → auto-recompile on change
|
||||
- [ ] **Clojure CLI compatibility** — `cljnim -M -e '...'`
|
||||
|
||||
### Milestone: nREPL с CIDER
|
||||
```bash
|
||||
# Стартираш nREPL
|
||||
cljnim nrepl :port 7888
|
||||
|
||||
# В Emacs с CIDER — connect и ползваш Clojure/Nim като обикновен REPL
|
||||
M-x cider-connect-clj
|
||||
```
|
||||
|
||||
## Фаза 7: Core Библиотека Разширение (Sprint — 4 седмици)
|
||||
|
||||
**Цел:** `clojure.core` parity достатъчна за реални проекти.
|
||||
|
||||
### Protocols & Multimethods
|
||||
- [ ] `defprotocol`, `extend-type`, `extend-protocol`
|
||||
- [ ] `defmulti`, `defmethod`, `prefer-method`
|
||||
- [ ] `satisfies?`
|
||||
|
||||
### Records & Types
|
||||
- [ ] `defrecord`, `deftype`
|
||||
- [ ] `defstruct`
|
||||
- [ ] Java-like `gen-class` → `gen-nim` (Nim type generation)
|
||||
|
||||
### Specs (clojure.spec)
|
||||
- [ ] `s/def`, `s/fdef`, `s/cat`, `s/alt`
|
||||
- [ ] `s/valid?`, `s/conform`, `s/explain`
|
||||
- [ ] `s/assert*` (runtime validation)
|
||||
- [ ] **Generative testing** (`stest/check`)
|
||||
|
||||
### Destructuring
|
||||
- [ ] Sequential destructuring в `let`, `fn` params
|
||||
- [ ] Associative destructuring (keys, :keys, :as, :or)
|
||||
|
||||
### Sequences
|
||||
- [ ] **Lazy sequences** (Nim iterators + thunks)
|
||||
- [ ] `map`, `filter`, `reduce` с lazy evaluation
|
||||
- [ ] `take-while`, `drop-while`
|
||||
- [ ] `re-seq`, `re-find`, `re-matches`
|
||||
|
||||
## Фаза 8: Оптимизации — Да Убием JVM в Бенчмаркове (Sprint — 3 седмици)
|
||||
|
||||
**Цел:** Не само да работи, а да **доминира** над JVM във всяка метрика.
|
||||
|
||||
### Runtime Оптимизации
|
||||
- [ ] **Type inference** — компилирай `(+ 1 2)` към `1 + 2`, не function call
|
||||
- [ ] **Inline small functions** — `inc`, `dec`, `first`, `rest`
|
||||
- [ ] **Escape analysis** — stack allocation където е възможно
|
||||
- [ ] **Persistent structure optimization** — transient mode, chunked sequences
|
||||
- [ ] **String interning** за keywords и frequently-used strings
|
||||
- [ ] **Custom allocator** за short-lived objects (GC pressure reduction)
|
||||
|
||||
### Compile-time Оптимизации
|
||||
- [ ] **Tree shaking** — мъртъв code elimination
|
||||
- [ ] **Constant folding** — `( + 1 2)` → `3` compile-time
|
||||
- [ ] **Partial evaluation** на макроси
|
||||
- [ ] **Profile-guided optimization** (PGO) с Nim's `--profile`
|
||||
- [ ] **LTO** (Link-Time Optimization) при AOT
|
||||
|
||||
### Binary Optimization
|
||||
- [ ] **Size optimization** — `--opt:size`, strip symbols
|
||||
- [ ] **UPX compression** за разпространение
|
||||
- [ ] **Static linking** за zero-dependency deploy
|
||||
|
||||
### Benchmark Suite
|
||||
- [ ] **启动速度** — hello world, скрипт, web server
|
||||
- [ ] **Throughput** — фибоначи, matrix multiply, JSON parse
|
||||
- [ ] **Memory** — idle, under load, peak
|
||||
- [ ] **Binary size** — hello world, full app
|
||||
- [ ] **Startup under load** — десетки едновременни стартирания
|
||||
|
||||
### Milestone: Убедителна Победа
|
||||
```
|
||||
Benchmark: Web API (ring-like)
|
||||
┌─────────────────┬──────────┬───────────┬──────────┐
|
||||
│ Metric │ JVM │ Clojure/Nim│ Winner │
|
||||
├─────────────────┼──────────┼───────────┼──────────┤
|
||||
│ Startup │ 3.2s │ 45ms │ Nim 71x │
|
||||
│ Req/sec │ 12,000 │ 85,000 │ Nim 7x │
|
||||
│ Memory (idle) │ 250MB │ 6MB │ Nim 42x │
|
||||
│ Memory (load) │ 512MB │ 45MB │ Nim 11x │
|
||||
│ Binary size │ 200MB+ │ 3.2MB │ Nim 62x │
|
||||
│ P99 latency │ 15ms │ 0.8ms │ Nim 19x │
|
||||
│ Cold start+req │ 4.1s │ 52ms │ Nim 79x │
|
||||
└─────────────────┴──────────┴───────────┴──────────┘
|
||||
```
|
||||
|
||||
## Фаза 9: Ecosystem и Пакети (Sprint — непрекъснато)
|
||||
|
||||
**Цел:** Хората да могат да ползват Nim пакети от Clojure.
|
||||
|
||||
### Package Manager
|
||||
- [ ] `cljnim.edn` — dependency файл (兼容 deps.edn формат)
|
||||
- [ ] **Nimble интеграция** — `{:deps {httpkit/nim {:mvn/version "1.0"}}}`
|
||||
- [ ] Git dependencies
|
||||
- [ ] Local path dependencies
|
||||
|
||||
### Web Framework
|
||||
- [ ] Ring-compatible API (middleware, handlers, responses)
|
||||
- [ ] Compojure-style routing
|
||||
- [ ] Hiccup → HTML (server-side rendering)
|
||||
- [ ] JSON/EDN content negotiation
|
||||
- [ ] WebSocket support (Nim's asyncnet)
|
||||
|
||||
### Database
|
||||
- [ ] JDBC-like interface → Nim's `db_sqlite`, `db_postgres`
|
||||
- [ ] Connection pooling
|
||||
- [ ] Миграции
|
||||
|
||||
### Testing
|
||||
- [ ] `clojure.test` compatibility (`deftest`, `testing`, `is`, `are`)
|
||||
- [ ] `test.check` — generative testing
|
||||
- [ ] Coverage reporting
|
||||
|
||||
## Фаза 10: Пазарно Позициониране
|
||||
|
||||
### Use Cases където Clojure/Nim **побеждава** JVM Clojure
|
||||
|
||||
1. **CLI инструменти** — instant startup, single binary, small size
|
||||
2. **Serverless / Lambda** — cold start е критичен (Nim: 50ms vs JVM: 5 сек)
|
||||
3. **Embedded системи** — Ferret доказа че Clojure работи на 2KB RAM
|
||||
4. **Microservices** — малки контейнери (5MB vs 200MB+)
|
||||
5. **Scripting** — Babashka-стил, но native
|
||||
6. **Game dev** — Nim има отлична FFI към SDL2/OpenGL
|
||||
7. **IoT** — ограничен ресурс, нужен е малък footprint
|
||||
|
||||
### Маркетинг Стратегия
|
||||
|
||||
- **"Clojure without the JVM tax"** — startup time demo
|
||||
- **Benchmarks page** — публични бенчмаркове vs JVM Clojure
|
||||
- **Migration guide** — "Как да преместиш проекта си от JVM към Nim"
|
||||
- **Clojure/conj lightning talk** — "What if Clojure started in 50ms?"
|
||||
- **Blog series** — "Building Clojure/Nim" (technical deep-dives)
|
||||
|
||||
---
|
||||
- [ ] **Atoms** (CAS)
|
||||
- [ ] **Agents**
|
||||
- [ ] **core.async** channels (упростен вариант)
|
||||
|
||||
## Текущ Фокус
|
||||
|
||||
**Фаза 5 — Конкурентност — Без GIL**
|
||||
**Фаза 1 — AI-First REPL**
|
||||
|
||||
Приоритет:
|
||||
1. Atoms с CAS
|
||||
2. Futures / Promises
|
||||
3. core.async (channels, go blocks)
|
||||
|
||||
Следващ milestone: `(let [a (atom 0)] (swap! a inc) @a)` → `1`
|
||||
Следваща задача: `cljnim repl --json` с batch evaluation и структурирани отговори.
|
||||
|
||||
+39
-23
@@ -1,17 +1,15 @@
|
||||
# Clojure/Nim Compiler
|
||||
import os, osproc
|
||||
import reader, emitter, types, macros
|
||||
# Clojure/Nim — AI-First Clojure Compiler
|
||||
import os, osproc, strutils
|
||||
import reader, emitter, types, repl
|
||||
|
||||
proc getLibPath(): string =
|
||||
let appDir = getAppDir()
|
||||
# Try relative to binary location
|
||||
let candidate1 = appDir / "lib"
|
||||
let candidate2 = appDir.parentDir / "lib"
|
||||
let candidate3 = getCurrentDir() / "lib"
|
||||
if dirExists(candidate1): return candidate1
|
||||
if dirExists(candidate2): return candidate2
|
||||
if dirExists(candidate3): return candidate3
|
||||
# Fallback: assume lib is in current dir
|
||||
return "lib"
|
||||
|
||||
proc compileFile*(inputPath: string, outputPath: string) =
|
||||
@@ -55,36 +53,38 @@ proc runFile*(inputPath: string) =
|
||||
quit(1)
|
||||
|
||||
proc main() =
|
||||
initBuiltinMacros()
|
||||
let args = commandLineParams()
|
||||
|
||||
if args.len == 0:
|
||||
echo "Clojure/Nim Compiler"
|
||||
echo "Clojure/Nim — AI-First Clojure Compiler"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " cljnim compile <file.clj> [output.nim] Compile to Nim"
|
||||
echo " cljnim run <file.clj> Compile and run"
|
||||
echo " cljnim compile <file.clj> [output.nim] Compile to Nim source"
|
||||
echo " cljnim run <file.clj> Compile and run binary"
|
||||
echo " cljnim read <file.clj> Parse and print AST"
|
||||
echo " cljnim repl [--json] Start REPL (human or AI mode)"
|
||||
echo " cljnim -e '<code>' Evaluate expression"
|
||||
echo " cljnim -M -e '<code>' Evaluate with full runtime"
|
||||
echo ""
|
||||
echo "REPL Commands:"
|
||||
echo " :quit, :q Exit REPL"
|
||||
echo " :defs List defined vars"
|
||||
echo " :clear Clear definitions"
|
||||
echo " :ns Show current namespace"
|
||||
echo ""
|
||||
echo "AI Mode (JSON):"
|
||||
echo " cljnim repl --json"
|
||||
echo " Input: {\"op\":\"eval\",\"form\":\"(+ 1 2)\"}"
|
||||
echo " Output: {\"status\":\"ok\",\"result\":{...},\"meta\":{...}}"
|
||||
quit(0)
|
||||
|
||||
let cmd = args[0]
|
||||
|
||||
# Handle -e and -M -e flags
|
||||
if cmd == "-e" or cmd == "-M":
|
||||
var code = ""
|
||||
var idx = 1
|
||||
if cmd == "-M" and args.len > 1 and args[1] == "-e":
|
||||
idx = 2
|
||||
elif cmd == "-e":
|
||||
idx = 1
|
||||
else:
|
||||
stderr.writeLine("Usage: cljnim [-M] -e '<code>'")
|
||||
quit(1)
|
||||
if idx >= args.len:
|
||||
# Handle -e flag
|
||||
if cmd == "-e":
|
||||
if args.len < 2:
|
||||
stderr.writeLine("Error: Missing expression after -e")
|
||||
quit(1)
|
||||
code = args[idx]
|
||||
let code = args[1]
|
||||
let tmpDir = getTempDir()
|
||||
let nimPath = tmpDir / "cljnim_expr.nim"
|
||||
let binPath = tmpDir / "cljnim_expr"
|
||||
@@ -122,6 +122,22 @@ proc main() =
|
||||
for form in forms:
|
||||
echo $form
|
||||
|
||||
of "repl":
|
||||
var mode = rmHuman
|
||||
for i in 1..<args.len:
|
||||
if args[i] == "--json":
|
||||
mode = rmJson
|
||||
elif args[i] == "--edn":
|
||||
mode = rmEdn
|
||||
var state = initReplState(mode)
|
||||
try:
|
||||
case mode
|
||||
of rmHuman: runHumanRepl(state)
|
||||
of rmJson: runJsonRepl(state)
|
||||
of rmEdn: runJsonRepl(state)
|
||||
finally:
|
||||
state.cleanup()
|
||||
|
||||
else:
|
||||
stderr.writeLine("Unknown command: " & cmd)
|
||||
quit(1)
|
||||
|
||||
+2
-2
@@ -940,9 +940,9 @@ proc emitProgram*(forms: seq[CljVal]): string =
|
||||
stripped.startsWith("try:") or stripped.startsWith("var "):
|
||||
mainForms.add(code)
|
||||
elif i == forms.len - 1:
|
||||
mainForms.add(indentStr(2) & "echo " & stripped)
|
||||
mainForms.add(indentStr(2) & "echo cljRepr(" & stripped & ")")
|
||||
else:
|
||||
mainForms.add(indentStr(2) & "discard " & stripped)
|
||||
mainForms.add(indentStr(2) & "discard cljRepr(" & stripped & ")")
|
||||
|
||||
var lines = headerLines
|
||||
# Add collected Nim imports
|
||||
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
# AI-First REPL for Clojure/Nim
|
||||
import os, osproc, strutils, json, times
|
||||
import reader, emitter, types
|
||||
|
||||
type
|
||||
ReplMode* = enum
|
||||
rmHuman, rmJson, rmEdn
|
||||
|
||||
ReplState* = object
|
||||
mode*: ReplMode
|
||||
ns*: string
|
||||
defs*: seq[CljVal]
|
||||
tempDir*: string
|
||||
sessionId*: string
|
||||
libPath*: string
|
||||
|
||||
proc getLibPath(): string =
|
||||
let appDir = getAppDir()
|
||||
let candidate1 = appDir / "lib"
|
||||
let candidate2 = appDir.parentDir / "lib"
|
||||
let candidate3 = getCurrentDir() / "lib"
|
||||
if dirExists(candidate1): return candidate1
|
||||
if dirExists(candidate2): return candidate2
|
||||
if dirExists(candidate3): return candidate3
|
||||
return "lib"
|
||||
|
||||
proc initReplState*(mode: ReplMode): ReplState =
|
||||
result.mode = mode
|
||||
result.ns = "user"
|
||||
result.defs = @[]
|
||||
result.tempDir = getTempDir() / "cljnim_repl_" & $getCurrentProcessId()
|
||||
createDir(result.tempDir)
|
||||
result.sessionId = $getCurrentProcessId() & "_" & $epochTime().int
|
||||
result.libPath = getLibPath()
|
||||
|
||||
proc cleanup*(state: ReplState) =
|
||||
try:
|
||||
removeDir(state.tempDir)
|
||||
except:
|
||||
discard
|
||||
|
||||
proc buildProgram(state: ReplState, form: CljVal, isDef: bool): string =
|
||||
var allForms = state.defs
|
||||
if isDef:
|
||||
allForms.add(form)
|
||||
|
||||
var header = @[
|
||||
"# Generated by Clojure/Nim REPL",
|
||||
"import cljnim_runtime",
|
||||
"",
|
||||
]
|
||||
|
||||
var mainCode = ""
|
||||
if not isDef:
|
||||
mainCode = emitter.emitExpr(form, 0)
|
||||
|
||||
var lines = header
|
||||
for d in allForms:
|
||||
lines.add(emitter.emitExpr(d, 0))
|
||||
|
||||
if not isDef and mainCode.len > 0:
|
||||
lines.add("")
|
||||
lines.add("when isMainModule:")
|
||||
let stripped = mainCode.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or stripped.startsWith("block:"):
|
||||
lines.add(" " & stripped)
|
||||
else:
|
||||
lines.add(" echo cljRepr(" & stripped & ")")
|
||||
elif isDef:
|
||||
lines.add("")
|
||||
lines.add("when isMainModule:")
|
||||
lines.add(" discard nil")
|
||||
|
||||
return lines.join("\n") & "\n"
|
||||
|
||||
proc compileAndRun(state: ReplState, nimCode: string): tuple[ok: bool, output: string, error: string] =
|
||||
let sourceFile = state.tempDir / "repl_input.nim"
|
||||
let binFile = state.tempDir / "repl_input"
|
||||
writeFile(sourceFile, nimCode)
|
||||
|
||||
let compileCmd = "nim c --path:" & quoteShell(state.libPath) & " -o:" & binFile & " " & sourceFile
|
||||
let (compileOut, compileExit) = execCmdEx(compileCmd)
|
||||
|
||||
if compileExit != 0 or not fileExists(binFile):
|
||||
return (false, "", compileOut)
|
||||
|
||||
let (runOut, runExit) = execCmdEx(binFile)
|
||||
if runExit != 0:
|
||||
return (false, "", runOut)
|
||||
return (true, runOut.strip(), "")
|
||||
|
||||
proc evalForm*(state: var ReplState, formStr: string): JsonNode =
|
||||
let startTime = epochTime()
|
||||
|
||||
var parsed: CljVal
|
||||
try:
|
||||
parsed = reader.read(formStr)
|
||||
except ReaderError as e:
|
||||
return %*{
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "reader/error",
|
||||
"message": e.msg,
|
||||
"form": formStr
|
||||
},
|
||||
"meta": {
|
||||
"ns": state.ns,
|
||||
"ms": (epochTime() - startTime) * 1000
|
||||
}
|
||||
}
|
||||
|
||||
let isDef = parsed.kind == ckList and parsed.items.len > 0 and
|
||||
parsed.items[0].kind == ckSymbol and
|
||||
parsed.items[0].symName in ["def", "defn"]
|
||||
|
||||
let nimCode = buildProgram(state, parsed, isDef)
|
||||
let (ok, output, error) = compileAndRun(state, nimCode)
|
||||
|
||||
let elapsed = (epochTime() - startTime) * 1000
|
||||
|
||||
if not ok:
|
||||
return %*{
|
||||
"status": "error",
|
||||
"error": {
|
||||
"type": "compiler/error",
|
||||
"message": error,
|
||||
"form": formStr
|
||||
},
|
||||
"meta": {
|
||||
"ns": state.ns,
|
||||
"ms": elapsed
|
||||
}
|
||||
}
|
||||
|
||||
if isDef:
|
||||
state.defs.add(parsed)
|
||||
let name = if parsed.items[1].kind == ckSymbol: parsed.items[1].symName else: "unknown"
|
||||
return %*{
|
||||
"status": "ok",
|
||||
"result": {
|
||||
"type": "var",
|
||||
"name": name,
|
||||
"ns": state.ns,
|
||||
"printed": "#\"" & state.ns & "/" & name & "\""
|
||||
},
|
||||
"meta": {
|
||||
"ns": state.ns,
|
||||
"ms": elapsed,
|
||||
"form": formStr
|
||||
}
|
||||
}
|
||||
|
||||
return %*{
|
||||
"status": "ok",
|
||||
"result": {
|
||||
"type": "unknown",
|
||||
"value": output,
|
||||
"printed": output
|
||||
},
|
||||
"meta": {
|
||||
"ns": state.ns,
|
||||
"ms": elapsed,
|
||||
"form": formStr
|
||||
}
|
||||
}
|
||||
|
||||
proc runHumanRepl*(state: var ReplState) =
|
||||
echo "Clojure/Nim REPL"
|
||||
echo "Type :quit to exit, :help for commands"
|
||||
echo ""
|
||||
|
||||
while true:
|
||||
stdout.write(state.ns & "> ")
|
||||
stdout.flushFile()
|
||||
let line = stdin.readLine()
|
||||
|
||||
if line.strip().len == 0:
|
||||
continue
|
||||
|
||||
let cmd = line.strip()
|
||||
|
||||
case cmd
|
||||
of ":quit", ":q":
|
||||
echo "Bye!"
|
||||
break
|
||||
of ":help", ":h":
|
||||
echo ":quit, :q Exit REPL"
|
||||
echo ":defs List defined vars"
|
||||
echo ":clear Clear all definitions"
|
||||
echo ":ns Show current namespace"
|
||||
of ":defs":
|
||||
for d in state.defs:
|
||||
if d.kind == ckList and d.items.len > 1 and d.items[1].kind == ckSymbol:
|
||||
echo " " & d.items[1].symName
|
||||
of ":clear":
|
||||
state.defs = @[]
|
||||
echo "Definitions cleared."
|
||||
of ":ns":
|
||||
echo state.ns
|
||||
else:
|
||||
let res = evalForm(state, cmd)
|
||||
if res["status"].getStr() == "ok":
|
||||
echo "=> " & res["result"]["printed"].getStr()
|
||||
else:
|
||||
echo "Error: " & res["error"]["message"].getStr()
|
||||
|
||||
proc runJsonRepl*(state: var ReplState) =
|
||||
var readyMsg = %*{ "status": "ready", "ns": state.ns, "mode": "json" }
|
||||
echo $readyMsg
|
||||
stdout.flushFile()
|
||||
|
||||
while true:
|
||||
let line = stdin.readLine()
|
||||
if line.strip().len == 0:
|
||||
continue
|
||||
|
||||
var req: JsonNode
|
||||
try:
|
||||
req = parseJson(line)
|
||||
except JsonParsingError as e:
|
||||
var err = %*{
|
||||
"status": "error",
|
||||
"error": { "type": "json/parse-error", "message": e.msg }
|
||||
}
|
||||
echo $err
|
||||
stdout.flushFile()
|
||||
continue
|
||||
|
||||
let op = req.getOrDefault("op").getStr("eval")
|
||||
|
||||
case op
|
||||
of "eval":
|
||||
let form = req.getOrDefault("form").getStr("")
|
||||
if form.len == 0:
|
||||
var err = %*{
|
||||
"status": "error",
|
||||
"error": { "type": "repl/missing-form", "message": "Missing :form field" }
|
||||
}
|
||||
echo $err
|
||||
else:
|
||||
let res = evalForm(state, form)
|
||||
if req.hasKey("request-id"):
|
||||
res["request-id"] = req["request-id"]
|
||||
echo $res
|
||||
|
||||
of "eval-batch":
|
||||
let forms = req.getOrDefault("forms")
|
||||
var results: seq[JsonNode] = @[]
|
||||
if forms.kind == JArray:
|
||||
for f in forms:
|
||||
let formStr = f.getStr("")
|
||||
if formStr.len > 0:
|
||||
results.add(evalForm(state, formStr))
|
||||
var batchRes = %*{ "status": "ok", "results": results }
|
||||
echo $batchRes
|
||||
|
||||
of "get-defs":
|
||||
var defs: seq[string] = @[]
|
||||
for d in state.defs:
|
||||
if d.kind == ckList and d.items.len > 1 and d.items[1].kind == ckSymbol:
|
||||
defs.add(d.items[1].symName)
|
||||
var defsRes = %*{ "status": "ok", "defs": defs, "ns": state.ns }
|
||||
echo $defsRes
|
||||
|
||||
of "clear":
|
||||
state.defs = @[]
|
||||
var clearRes = %*{ "status": "ok", "cleared": true }
|
||||
echo $clearRes
|
||||
|
||||
of "quit":
|
||||
var quitRes = %*{ "status": "ok", "bye": true }
|
||||
echo $quitRes
|
||||
stdout.flushFile()
|
||||
break
|
||||
|
||||
else:
|
||||
var unkRes = %*{
|
||||
"status": "error",
|
||||
"error": { "type": "repl/unknown-op", "message": "Unknown op: " & op }
|
||||
}
|
||||
echo $unkRes
|
||||
|
||||
stdout.flushFile()
|
||||
Reference in New Issue
Block a user