diff --git a/AI_FIRST.md b/AI_FIRST.md new file mode 100644 index 0000000..cb72648 --- /dev/null +++ b/AI_FIRST.md @@ -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` | diff --git a/ROADMAP.md b/ROADMAP.md index 768a170..c5f13fb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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 и структурирани отговори. diff --git a/src/cljnim.nim b/src/cljnim.nim index 8209bb6..6b97552 100644 --- a/src/cljnim.nim +++ b/src/cljnim.nim @@ -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 [output.nim] Compile to Nim" - echo " cljnim run Compile and run" + echo " cljnim compile [output.nim] Compile to Nim source" + echo " cljnim run Compile and run binary" echo " cljnim read Parse and print AST" + echo " cljnim repl [--json] Start REPL (human or AI mode)" echo " cljnim -e '' Evaluate expression" - echo " cljnim -M -e '' 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 ''") - 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.. 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()