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 | Коефициент |
|
**Цел:** REPL, който AI може да ползва през терминал без редактор.
|
||||||
|---|---|---|---|
|
|
||||||
| 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 по-малко** |
|
|
||||||
|
|
||||||
---
|
### 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] Определяне на цели и архитектура
|
### 1.3 File Operations
|
||||||
- [x] Избор на подход (AOT компилатор)
|
- [ ] `(file/read "path")` — четене на файл
|
||||||
- [x] Създаване на структура на проекта
|
- [ ] `(file/write "path" "content")` — запис на файл
|
||||||
- [x] CI/CD pipeline (GitLab CI)
|
- [ ] `(file/append "path" "content")` — добавяне
|
||||||
- [x] Тестов framework (unittest)
|
- [ ] `(file/ls "dir")` — листване на директория
|
||||||
- [x] **Benchmarks suite** — сравняване с JVM Clojure
|
- [ ] `(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
|
**Цел:** Истински Clojure immutable структури.
|
||||||
- [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 (`#'`, `#_`)
|
|
||||||
|
|
||||||
### Emitter (минимален)
|
- [ ] **Persistent Vector** (Hash Array Mapped Trie)
|
||||||
- [x] `println`, `def`, `defn`, `fn`, `let`, `if`, `do`
|
- [ ] **Persistent Map** (HAMT)
|
||||||
- [x] `when`, `cond`, `loop`/`recur`
|
- [ ] **Persistent Set**
|
||||||
- [x] `try`/`catch`/`finally`
|
- [ ] `conj`, `assoc`, `dissoc`, `get`, `get-in`
|
||||||
- [x] Map литерали в emitter
|
- [ ] `nth`, `first`, `rest`, `last`, `count`
|
||||||
- [x] `str`, `not`, `not=`, `>=`, `<=`
|
|
||||||
|
|
||||||
### Milestone: Hello World Benchmark ✅
|
## Фаза 3: Clojure Core Библиотека
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Фаза 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
|
### 3.2 Низове и IO
|
||||||
- [x] **CljVector** — seq-based с copy-on-write семантика
|
- [ ] `str`, `pr-str`, `println`, `prn`
|
||||||
- [x] **CljMap** — seq-based key/value с copy-on-write
|
- [ ] `slurp`, `spit`
|
||||||
- [ ] **HAMT** (Hash Array Mapped Trie) — за O(log₃₂ n) операции
|
- [ ] `read-line`
|
||||||
|
|
||||||
### Core функции — Collections
|
### 3.3 Мета
|
||||||
- [x] `map`, `filter`, `reduce`, `mapv`
|
- [ ] `meta`, `with-meta`, `vary-meta`
|
||||||
- [x] `first`, `rest`, `next`, `last`, `nth`
|
- [ ] `type`, `instance?`
|
||||||
- [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?`
|
|
||||||
|
|
||||||
### Core функции — Strings
|
## Фаза 4: Макро Система
|
||||||
- [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`
|
|
||||||
|
|
||||||
### Core функции — Numbers & Logic
|
**Цел:** `defmacro` да работи.
|
||||||
- [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`
|
|
||||||
|
|
||||||
### Core функции — IO & Misc
|
- [ ] `macroexpand`, `macroexpand-1`
|
||||||
- [x] `identity`, `type`
|
- [ ] `syntax-quote`, `unquote`, `unquote-splicing`
|
||||||
- [x] `atom`, `deref`, `reset!`, `swap!`
|
- [ ] `gensym`
|
||||||
- [x] `throw`/`try`/`catch`/`finally`
|
- [ ] Core макроси: `->`, `->>`, `and`, `or`, `when`, `cond`
|
||||||
```clojure
|
|
||||||
;; РЕЗУЛТАТ: 29x по-бърз от JVM Clojure!
|
|
||||||
(defn fib [n]
|
|
||||||
(if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2)))))
|
|
||||||
|
|
||||||
(println (fib 30))
|
## Фаза 5: Nim Interop
|
||||||
```
|
|
||||||
```bash
|
|
||||||
time cljnim run fib.clj # ~290ms (with Nim compile)
|
|
||||||
time cljnim compile fib.clj && time ./fib # ~24ms (AOT)
|
|
||||||
time clojure fib.clj # ~715ms
|
|
||||||
```
|
|
||||||
|
|
||||||
## Фаза 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"])` — извикване
|
||||||
|
|
||||||
### Специални форми
|
## Фаза 6: AOT Компилация + REPL
|
||||||
- [x] `def`, `fn`/`defn`, `let`/`letfn`
|
|
||||||
- [x] `if`/`when`/`cond`
|
|
||||||
- [x] `do`, `quote`, `var`, `set!`
|
|
||||||
- [x] `loop`/`recur`
|
|
||||||
- [x] `throw`/`try`/`catch`/`finally`
|
|
||||||
|
|
||||||
### Макро 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 макроси
|
- [ ] AOT компилация на цели проекти
|
||||||
- [x] `->`, `->>`, `as->`, `some->`, `some->>`
|
- [ ] Кеширане на компилирани модули
|
||||||
- [x] `cond`, `cond->`, `cond->>`
|
- [ ] REPL в self-hosted режим (като ClojureScript)
|
||||||
- [x] `and`, `or`, `when-let`, `if-let`
|
|
||||||
- [x] `when`, `when-not`, `doto`, `with-open`
|
|
||||||
- [x] `for`, `doseq`, `dotimes`
|
|
||||||
- [x] `comment`, `assert`, `defn-`
|
|
||||||
|
|
||||||
### Milestone: Макро Бенчмарк ✅
|
## Фаза 7: Многонишковост
|
||||||
```clojure
|
|
||||||
;; Работи без промяна от JVM Clojure!
|
|
||||||
(defmacro unless [condition & body]
|
|
||||||
(list 'if condition nil (cons 'do body)))
|
|
||||||
|
|
||||||
(unless false (println "Macros work!")) ; => "Macros work!"
|
- [ ] **Atoms** (CAS)
|
||||||
```
|
- [ ] **Agents**
|
||||||
|
- [ ] **core.async** channels (упростен вариант)
|
||||||
## Фаза 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)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Текущ Фокус
|
## Текущ Фокус
|
||||||
|
|
||||||
**Фаза 5 — Конкурентност — Без GIL**
|
**Фаза 1 — AI-First REPL**
|
||||||
|
|
||||||
Приоритет:
|
Следваща задача: `cljnim repl --json` с batch evaluation и структурирани отговори.
|
||||||
1. Atoms с CAS
|
|
||||||
2. Futures / Promises
|
|
||||||
3. core.async (channels, go blocks)
|
|
||||||
|
|
||||||
Следващ milestone: `(let [a (atom 0)] (swap! a inc) @a)` → `1`
|
|
||||||
|
|||||||
+39
-23
@@ -1,17 +1,15 @@
|
|||||||
# Clojure/Nim Compiler
|
# Clojure/Nim — AI-First Clojure Compiler
|
||||||
import os, osproc
|
import os, osproc, strutils
|
||||||
import reader, emitter, types, macros
|
import reader, emitter, types, repl
|
||||||
|
|
||||||
proc getLibPath(): string =
|
proc getLibPath(): string =
|
||||||
let appDir = getAppDir()
|
let appDir = getAppDir()
|
||||||
# Try relative to binary location
|
|
||||||
let candidate1 = appDir / "lib"
|
let candidate1 = appDir / "lib"
|
||||||
let candidate2 = appDir.parentDir / "lib"
|
let candidate2 = appDir.parentDir / "lib"
|
||||||
let candidate3 = getCurrentDir() / "lib"
|
let candidate3 = getCurrentDir() / "lib"
|
||||||
if dirExists(candidate1): return candidate1
|
if dirExists(candidate1): return candidate1
|
||||||
if dirExists(candidate2): return candidate2
|
if dirExists(candidate2): return candidate2
|
||||||
if dirExists(candidate3): return candidate3
|
if dirExists(candidate3): return candidate3
|
||||||
# Fallback: assume lib is in current dir
|
|
||||||
return "lib"
|
return "lib"
|
||||||
|
|
||||||
proc compileFile*(inputPath: string, outputPath: string) =
|
proc compileFile*(inputPath: string, outputPath: string) =
|
||||||
@@ -55,36 +53,38 @@ proc runFile*(inputPath: string) =
|
|||||||
quit(1)
|
quit(1)
|
||||||
|
|
||||||
proc main() =
|
proc main() =
|
||||||
initBuiltinMacros()
|
|
||||||
let args = commandLineParams()
|
let args = commandLineParams()
|
||||||
|
|
||||||
if args.len == 0:
|
if args.len == 0:
|
||||||
echo "Clojure/Nim Compiler"
|
echo "Clojure/Nim — AI-First Clojure Compiler"
|
||||||
|
echo ""
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo " cljnim compile <file.clj> [output.nim] Compile to Nim"
|
echo " cljnim compile <file.clj> [output.nim] Compile to Nim source"
|
||||||
echo " cljnim run <file.clj> Compile and run"
|
echo " cljnim run <file.clj> Compile and run binary"
|
||||||
echo " cljnim read <file.clj> Parse and print AST"
|
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 -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)
|
quit(0)
|
||||||
|
|
||||||
let cmd = args[0]
|
let cmd = args[0]
|
||||||
|
|
||||||
# Handle -e and -M -e flags
|
# Handle -e flag
|
||||||
if cmd == "-e" or cmd == "-M":
|
if cmd == "-e":
|
||||||
var code = ""
|
if args.len < 2:
|
||||||
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:
|
|
||||||
stderr.writeLine("Error: Missing expression after -e")
|
stderr.writeLine("Error: Missing expression after -e")
|
||||||
quit(1)
|
quit(1)
|
||||||
code = args[idx]
|
let code = args[1]
|
||||||
let tmpDir = getTempDir()
|
let tmpDir = getTempDir()
|
||||||
let nimPath = tmpDir / "cljnim_expr.nim"
|
let nimPath = tmpDir / "cljnim_expr.nim"
|
||||||
let binPath = tmpDir / "cljnim_expr"
|
let binPath = tmpDir / "cljnim_expr"
|
||||||
@@ -122,6 +122,22 @@ proc main() =
|
|||||||
for form in forms:
|
for form in forms:
|
||||||
echo $form
|
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:
|
else:
|
||||||
stderr.writeLine("Unknown command: " & cmd)
|
stderr.writeLine("Unknown command: " & cmd)
|
||||||
quit(1)
|
quit(1)
|
||||||
|
|||||||
+2
-2
@@ -940,9 +940,9 @@ proc emitProgram*(forms: seq[CljVal]): string =
|
|||||||
stripped.startsWith("try:") or stripped.startsWith("var "):
|
stripped.startsWith("try:") or stripped.startsWith("var "):
|
||||||
mainForms.add(code)
|
mainForms.add(code)
|
||||||
elif i == forms.len - 1:
|
elif i == forms.len - 1:
|
||||||
mainForms.add(indentStr(2) & "echo " & stripped)
|
mainForms.add(indentStr(2) & "echo cljRepr(" & stripped & ")")
|
||||||
else:
|
else:
|
||||||
mainForms.add(indentStr(2) & "discard " & stripped)
|
mainForms.add(indentStr(2) & "discard cljRepr(" & stripped & ")")
|
||||||
|
|
||||||
var lines = headerLines
|
var lines = headerLines
|
||||||
# Add collected Nim imports
|
# 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