Add AI-first REPL with JSON mode

This commit is contained in:
2026-05-08 16:51:05 +03:00
parent 87d6028487
commit 5971c062d1
5 changed files with 595 additions and 363 deletions
+194
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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()