docs: update documentation for May 12 compiler fixes
- CLJNIM_RECOMMENDATIONS.md: mark all 12 recommendations as implemented - README.md: add --lib-path, first-class functions, multi-arity, rest params - User guides (EN/BG): add sections for docstrings, multi-arity, rest params, keyword-as-function, first-class functions, loop/recur, --lib-path flag - TASKS.md: add Phase 11 with all BRing compiler fixes
This commit is contained in:
+151
-109
@@ -1,171 +1,170 @@
|
|||||||
# Препоръки към cljnim компилатора
|
# Препоръки към cljnim компилатора
|
||||||
|
|
||||||
> Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.
|
> Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.
|
||||||
|
>
|
||||||
|
> **Последно обновяване:** 12 май 2026
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Поддръжка на docstrings в `defn`
|
## 1. Поддръжка на docstrings в `defn` ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(defn greet "Says hello" [name]
|
(defn greet "Says hello" [name]
|
||||||
(str "Hello " name))
|
(str "Hello " name))
|
||||||
```
|
```
|
||||||
Гърми с:
|
Гърмеше с:
|
||||||
```
|
```
|
||||||
Error: unhandled exception: defn params must be a vector [EmitterError]
|
Error: unhandled exception: defn params must be a vector [EmitterError]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Причина:** `defn` в `emitter.nim` очаква вторият аргумент да е вектор с параметри. Docstring-ът се третира като име на параметър.
|
**Решение:** В `emitSpecialForm` за `defn` сега се проверява дали `items[2]` е стринг (docstring) и ако да — се пропуска и се взема `items[3]` като параметри.
|
||||||
|
|
||||||
**Препоръка:** В `emitSpecialForm` за `defn` да се провери дали `items[2]` е стринг (docstring) и ако да — да се пропусне и да се вземе `items[3]` като параметри.
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
**Workaround за сега:** Не използвайте docstrings в `defn`. Пишете ги като обикновени коментари `;` преди функцията.
|
(defn greet "Says hello" [name]
|
||||||
|
(str "Hello " name))
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. Поддръжка на multi-arity `defn`
|
## 2. Поддръжка на multi-arity `defn` ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(defn greet
|
(defn greet
|
||||||
([name] (greet name "Hello"))
|
([name] (greet name "Hello"))
|
||||||
([name greeting] (str greeting " " name)))
|
([name greeting] (str greeting " " name)))
|
||||||
```
|
```
|
||||||
Гърми с:
|
Гърмеше с:
|
||||||
```
|
```
|
||||||
Error: unhandled exception: defn params must be a vector [EmitterError]
|
Error: unhandled exception: defn params must be a vector [EmitterError]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Причина:** Multi-arity синтаксисът не се разпознава. `items[2]` е списък `([name] ...)`, а не вектор.
|
**Решение:** Multi-arity синтаксисът сега се разпознава. Генерира се dispatch по `args.len` в Nim.
|
||||||
|
|
||||||
**Препоръка:** Да се добави логика в `emitSpecialForm` за `defn`, която проверява дали `items[2]` е списък от arity тела. Ако да — да се генерира Nim overload или dispatch по `args.len`.
|
**Пример (сега работи):**
|
||||||
|
|
||||||
**Workaround за сега:** Използвайте отделни функции за всяка arity:
|
|
||||||
```clojure
|
```clojure
|
||||||
(defn greet-default [name]
|
(defn greet
|
||||||
(greet name "Hello"))
|
([name] (greet name "Hello"))
|
||||||
|
([name greeting] (str greeting " " name)))
|
||||||
|
|
||||||
(defn greet [name greeting]
|
(greet "Alice") ;; => "Hello Alice"
|
||||||
(str greeting " " name))
|
(greet "Alice" "Hi") ;; => "Hi Alice"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. `(:key map)` синтаксис (keyword като функция)
|
## 3. `(:key map)` синтаксис (keyword като функция) ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(:request-method req) ; Clojure стандарт
|
(:request-method req) ; Clojure стандарт
|
||||||
```
|
```
|
||||||
Генерира:
|
Генерираше `cljApply(...)` вместо `cljGet(...)` и гърмеше с:
|
||||||
```nim
|
|
||||||
cljApply(cljKeyword("request-method"), cljList(@[req]))
|
|
||||||
```
|
|
||||||
А `cljApply` изисква първият аргумент да е `ckFn` и гърми с:
|
|
||||||
```
|
```
|
||||||
apply requires a function
|
apply requires a function
|
||||||
```
|
```
|
||||||
|
|
||||||
**Причина:** Когато първият елемент на списък е `ckKeyword`, `emitSpecialForm` го третира като "user-defined function call" и генерира `cljApply` вместо `cljGet`.
|
**Решение:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, сега се генерира `cljGet(args[0], keyword)` вместо `cljApply`.
|
||||||
|
|
||||||
**Препоръка:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, да се генерира `cljGet(args[0], keyword)` вместо `cljApply`.
|
**Пример (сега работи):**
|
||||||
|
|
||||||
**Workaround за сега:** Пишете явно:
|
|
||||||
```clojure
|
```clojure
|
||||||
(get req :request-method)
|
(def req {:request-method :get :uri "/hello"})
|
||||||
|
(:request-method req) ;; => :get
|
||||||
|
(:uri req) ;; => "/hello"
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. `&` rest параметри в `defn`
|
## 4. `&` rest параметри в `defn` ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(defn greet [name & [greeting]]
|
(defn greet [name & [greeting]]
|
||||||
...)
|
...)
|
||||||
```
|
```
|
||||||
Гърми с:
|
Гърмеше с:
|
||||||
```
|
```
|
||||||
Error: unhandled exception: defn params must be symbols [EmitterError]
|
Error: unhandled exception: defn params must be symbols [EmitterError]
|
||||||
```
|
```
|
||||||
|
|
||||||
**Причина:** `&` не се разпознава като специален символ в параметрите.
|
**Решение:** `&` сега се разпознава като специален символ в параметрите. Генерира се Nim код, който приема `seq[CljVal]` и разпределя аргументите.
|
||||||
|
|
||||||
**Препоръка:** Да се добави поддръжка за variadic параметри чрез `&`. Може да се генерира Nim код, който приема `seq[CljVal]` и разпределя аргументите.
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
|
(defn sum-all [x & rest]
|
||||||
|
(+ x (reduce + rest)))
|
||||||
|
|
||||||
**Workaround за сега:** Използвайте фиксиран брой аргументи или подавайте колекции (вектори/map-ове) като един аргумент.
|
(sum-all 1 2 3 4) ;; => 10
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Nim interop — mangle на имената
|
## 5. Nim interop — mangle на имената ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(nim/bring_http/run-server handler port)
|
(nim/bring_http/run-server handler port)
|
||||||
```
|
```
|
||||||
Генерира:
|
Генерираше:
|
||||||
```nim
|
```nim
|
||||||
run-server(handler, port)
|
run-server(handler, port)
|
||||||
```
|
```
|
||||||
Но `-` не е валиден символ в Nim идентификатори. Гърми при компилация на Nim.
|
А `-` не е валиден символ в Nim идентификатори.
|
||||||
|
|
||||||
**Причина:** В `emitSpecialForm` за `nim/` interop:
|
**Решение:** В `emitSpecialForm` за `nim/` interop се прилага `sanitizeNimIdent()`, която заменя `-` с `_` и премахва други невалидни символи за Nim.
|
||||||
```nim
|
|
||||||
var funcChain = parts[2..^1].join(".")
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
|
(nim/bring_http/run_server handler port) ;; run_server в Nim
|
||||||
|
(nim/strutils/to_upper "hello") ;; to_upper в Nim
|
||||||
```
|
```
|
||||||
Името се предава директно без mangle.
|
|
||||||
|
|
||||||
**Препоръка:** Да се приложи `mangleName()` или подобна функция върху `funcChain`, която заменя `-` с `_` и премахва други невалидни символи за Nim.
|
|
||||||
|
|
||||||
**Workaround за сега:** В Nim модулите използвайте само `camelCase` или `snake_case` имена без тирета.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. `getLibPath()` — търсене на `lib/` директория
|
## 6. `getLibPath()` — търсене на `lib/` директория ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):** `getLibPath()` винаги връщаше `appDir/lib`, което правеше невъзможно проекти от други директории да използват свои `lib/` пътища.
|
||||||
```nim
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
Когато `cljnim` е инсталиран в `/home/ziko/lispnim/`, `candidate1` винаги е `/home/ziko/lispnim/lib` и връща това. Проекти от други директории не могат да добавят свои `lib/` пътища.
|
**Решение:**
|
||||||
|
- Добавен `--lib-path <dir>` глобален CLI флаг.
|
||||||
|
- `getLibPath()` сега проверява в следния ред:
|
||||||
|
1. CLI `--lib-path` override
|
||||||
|
2. `CLJNIM_LIB_PATH` environment variable
|
||||||
|
3. Текуща директория `lib/`
|
||||||
|
4. Приложение `lib/`
|
||||||
|
|
||||||
**Препоръка:** Да се поддържа `CLJNIM_LIB_PATH` environment variable или `--lib-path` CLI флаг. Алтернативно, `candidate3` (текущата директория) да се проверява първи, преди `appDir`.
|
**Пример:**
|
||||||
|
|
||||||
**Workaround за сега:** Символна връзка на Nim модулите в `cljnim/lib/`:
|
|
||||||
```bash
|
```bash
|
||||||
ln -s /path/to/project/lib/my_module.nim /home/ziko/lispnim/lib/my_module.nim
|
# Със собствена lib директория
|
||||||
|
./cljnim --lib-path ./my_project/lib run app.clj
|
||||||
|
|
||||||
|
# С environment variable
|
||||||
|
export CLJNIM_LIB_PATH=./my_project/lib
|
||||||
|
./cljnim run app.clj
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Поддръжка на `:paths` в `deps.edn`
|
## 7. Поддръжка на `:paths` в `deps.edn` ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:** `deps.nim` парсира само `:deps` от `deps.edn`, но не и `:paths`.
|
**Проблем (преди):** `deps.nim` парсираше само `:deps` от `deps.edn`, но не и `:paths`.
|
||||||
|
|
||||||
**Причина:**
|
**Решение:** Добавен е парсинг на `:paths` от `deps.edn` и се включват в `searchPaths` при компилация.
|
||||||
```nim
|
|
||||||
proc parseDepsMap(depsMap: CljVal): seq[Dependency] =
|
**Пример (сега работи):**
|
||||||
# Парсира само :deps
|
```clojure
|
||||||
|
;; deps.edn
|
||||||
|
{:paths ["src" "lib"]
|
||||||
|
:deps {org.clojure/core.async {:mvn/version "1.6.681"}}}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Препоръка:** Да се добави парсинг на `:paths` и да се включват в `searchPaths` при компилация.
|
|
||||||
|
|
||||||
**Workaround за сега:** Clojure файловете трябва да са в структура, която съвпада с namespace имената, относително към текущата директория.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. `loop` с `if/else` — проблеми с `discard`
|
## 8. `loop` с `if/else` — проблеми с `discard` ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:**
|
**Проблем (преди):**
|
||||||
```clojure
|
```clojure
|
||||||
(loop [pairs (seq headers)]
|
(loop [pairs (seq headers)]
|
||||||
(if (empty? pairs)
|
(if (empty? pairs)
|
||||||
@@ -175,71 +174,114 @@ proc parseDepsMap(depsMap: CljVal): seq[Dependency] =
|
|||||||
v
|
v
|
||||||
(recur (rest pairs))))))
|
(recur (rest pairs))))))
|
||||||
```
|
```
|
||||||
|
Гърмеше с:
|
||||||
Генерира код, в който `cljNil()` в `else` клона не е discard-нат правилно и Nim гърми с:
|
|
||||||
```
|
```
|
||||||
expression 'cljNil()' is of type 'CljVal' and has to be used (or discarded)
|
expression 'cljNil()' is of type 'CljVal' and has to be used (or discarded)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Причина:** `emitSpecialForm` за `loop` добавя `break` след тялото, но `if/else` вътре в `while true:` блок не винаги връща стойност във всички клонове.
|
**Решение:**
|
||||||
|
- Добавен е `loopResult` променлива, която събира резултата.
|
||||||
|
- Подобрено е генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност правилно.
|
||||||
|
- Добавена е `needsValue` логика — `loop` се wrap-ва в IIFE `(proc(): CljVal = ...)()` когато е в expression context (напр. като аргумент на функция, в `let` RHS, или като последна стойност в програма).
|
||||||
|
|
||||||
**Препоръка:** Да се подобри генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност или да се използва `result = ...` pattern.
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
|
(defn find-header [headers name]
|
||||||
|
(loop [pairs (seq headers)]
|
||||||
|
(if (empty? pairs)
|
||||||
|
nil
|
||||||
|
(let [[k v] (first pairs)]
|
||||||
|
(if (= k name)
|
||||||
|
v
|
||||||
|
(recur (rest pairs)))))))
|
||||||
|
|
||||||
**Workaround за сега:** Използвайте `filter` + `first` вместо ръчен `loop`/`recur`.
|
(find-header [[:content-type "text/html"] [:accept "*/*"]] :accept)
|
||||||
|
;; => "*/*"
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Конфликти на имена между Clojure и Nim
|
## 9. Конфликти на имена между Clojure и Nim ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи:
|
**Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи:
|
||||||
- `parseQueryString` ≡ `parse_query_string` ≡ `parsequerystring`
|
- `parseQueryString` ≡ `parse_query_string` ≡ `parsequerystring`
|
||||||
|
|
||||||
Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име. Това води до:
|
Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име.
|
||||||
- Презаписване на функцията
|
|
||||||
- Безкрайна рекурсия, ако едната извиква другата
|
|
||||||
|
|
||||||
**Препоръка:** Да се добави prefix към всички генерирани Nim процедури, например `clj_`:
|
**Решение:** Добавен е `clj_` prefix към всички генерирани Nim процедури от Clojure код:
|
||||||
```nim
|
```nim
|
||||||
proc clj_parse_query_string(...) ; вместо parse_query_string
|
proc clj_parse_query_string(...) ; вместо parse_query_string
|
||||||
```
|
```
|
||||||
|
За `nim/` interop се използва `sanitizeNimIdent()` без prefix, така че native Nim имената да не се чупят.
|
||||||
**Workaround за сега:** В Nim runtime модулите използвайте уникални имена с префикс (напр. `bringParseQueryString`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. `when isMainModule` в генерирания код
|
## 10. `when isMainModule` в генерирания код ✅ Имплементирано
|
||||||
|
|
||||||
**Проблем:** `when isMainModule` в края на генерирания Nim файл означава, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни. Това прави невъзможно създаването на "библиотечни" Clojure файлове, които съдържат и дефиниции, и изпълним код.
|
**Проблем (преди):** `when isMainModule` в края на генерирания Nim файл означаваше, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни.
|
||||||
|
|
||||||
**Препоръка:** Да се добави флаг `--lib` или подобен, който да генерира код без `when isMainModule` за библиотечни файлове.
|
**Решение:** `emitProgramLib` сега skip-ва `when isMainModule` guard, което позволява генерирането на "библиотечни" Clojure файлове.
|
||||||
|
|
||||||
**Workaround за сега:** Използвайте `cljnim compile file.clj output.nim` и после ръчно `nim c output.nim`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Поддръжка на `try`/`catch`/`finally`
|
## 11. Поддръжка на `try`/`catch`/`finally` ✅ Имплементирано
|
||||||
|
|
||||||
**Статус:** Изглежда се поддържа, но не беше тествана обстойно по време на разработката на BRing.
|
**Решение:**
|
||||||
|
- `finally` блок сега правилно discard-ва expression резултатите.
|
||||||
|
- `catch` handler използва оригиналното име за scope tracking.
|
||||||
|
|
||||||
**Препоръка:** Да се добавят тестове за exception handling в HTTP контекст (грешки при парсиране, мрежови грешки).
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
|
(try
|
||||||
|
(risky-operation)
|
||||||
|
(catch Exception e
|
||||||
|
(println "Error:" (:message e)))
|
||||||
|
(finally
|
||||||
|
(cleanup)))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. First-class функции (defn като стойности) ✅ Имплементирано
|
||||||
|
|
||||||
|
**Проблем (преди):** User-defined `defn` функции не можеха да се подават като стойности на други функции (напр. `map`, `filter`, `apply`).
|
||||||
|
|
||||||
|
**Решение:**
|
||||||
|
- Проследяват се арностите на функциите в `definedFnArities` таблица.
|
||||||
|
- Когато символ на `defn` се използва като стойност, се генерира `cljFn` wrapper:
|
||||||
|
- Multi-arity / rest: `cljFn(procName)` (вече е `seq[CljVal]`)
|
||||||
|
- Single-arity: `cljFn(proc(args): procName(args[0], ...))`
|
||||||
|
- Директни извиквания остават непроменени (fast path).
|
||||||
|
|
||||||
|
**Пример (сега работи):**
|
||||||
|
```clojure
|
||||||
|
(defn square [x] (* x x))
|
||||||
|
|
||||||
|
(map square [1 2 3 4]) ;; => [1 4 9 16]
|
||||||
|
(filter odd? [1 2 3 4]) ;; => [1 3]
|
||||||
|
(apply + [1 2 3]) ;; => 6
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Обобщена таблица
|
## Обобщена таблица
|
||||||
|
|
||||||
| # | Проблем | Трудност | Приоритет |
|
| # | Проблем | Статус | Commit |
|
||||||
|---|---------|----------|-----------|
|
|---|---------|--------|--------|
|
||||||
| 1 | Docstrings в `defn` | Лесно | Висок |
|
| 1 | Docstrings в `defn` | ✅ Готово | e36bf4e |
|
||||||
| 2 | Multi-arity `defn` | Средно | Висок |
|
| 2 | Multi-arity `defn` | ✅ Готово | e36bf4e |
|
||||||
| 3 | `(:key map)` синтаксис | Лесно | Висок |
|
| 3 | `(:key map)` синтаксис | ✅ Готово | e36bf4e |
|
||||||
| 4 | `&` rest параметри | Средно | Среден |
|
| 4 | `&` rest параметри | ✅ Готово | e36bf4e |
|
||||||
| 5 | Nim interop mangle | Лесно | Висок |
|
| 5 | Nim interop mangle | ✅ Готово | e36bf4e, 3aaf3c0 |
|
||||||
| 6 | `getLibPath()` | Лесно | Среден |
|
| 6 | `getLibPath()` / `--lib-path` | ✅ Готово | e36bf4e, 9a8519d |
|
||||||
| 7 | `:paths` в deps.edn | Средно | Среден |
|
| 7 | `:paths` в deps.edn | ✅ Готово | e36bf4e |
|
||||||
| 8 | `loop` + `if/else` | Средно | Среден |
|
| 8 | `loop` + `if/else` discard | ✅ Готово | e36bf4e, a1a65f1 |
|
||||||
| 9 | Конфликти на имена | Средно | Висок |
|
| 9 | Конфликти на имена (`clj_` prefix) | ✅ Готово | fb8928e, 3aaf3c0 |
|
||||||
| 10 | `when isMainModule` | Лесно | Нисък |
|
| 10 | `when isMainModule` | ✅ Готово | e36bf4e |
|
||||||
|
| 11 | `try`/`catch`/`finally` | ✅ Готово | fb8928e |
|
||||||
|
| 12 | First-class `defn` функции | ✅ Готово | aac39a0 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Документът е съставен по време на разработката на [BRing](https://github.com/) — нативна Clojure уеб библиотека за cljnim.*
|
*Документът е съставен по време на разработката на [BRing](https://github.com/) — нативна Clojure уеб библиотека за cljnim.*
|
||||||
|
*Последно обновяване: 12 май 2026*
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ Hello, native world!
|
|||||||
| 🇬🇧 English | [docs/en/index.md](docs/en/index.md) | Getting started, architecture, AI integration, API reference |
|
| 🇬🇧 English | [docs/en/index.md](docs/en/index.md) | Getting started, architecture, AI integration, API reference |
|
||||||
| 🇧🇬 Български | [docs/bg/index.md](docs/bg/index.md) | Първи стъпки, архитектура, AI интеграция, API справочник |
|
| 🇧🇬 Български | [docs/bg/index.md](docs/bg/index.md) | Първи стъпки, архитектура, AI интеграция, API справочник |
|
||||||
| 🧪 Test Suite | [docs/en/07-clojure-test-suite.md](docs/en/07-clojure-test-suite.md) | Cross-dialect compliance with [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) |
|
| 🧪 Test Suite | [docs/en/07-clojure-test-suite.md](docs/en/07-clojure-test-suite.md) | Cross-dialect compliance with [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) |
|
||||||
|
| 🛠️ Recommendations | [CLJNIM_RECOMMENDATIONS.md](CLJNIM_RECOMMENDATIONS.md) | Compiler limitations, workarounds, and fixes (Bulgarian) |
|
||||||
|
|
||||||
## ✨ What Makes This Unique?
|
## ✨ What Makes This Unique?
|
||||||
|
|
||||||
@@ -127,6 +128,11 @@ No JNI, no JVM bridging, no Java interop overhead.
|
|||||||
| AI integration (DeepSeek, OpenAI, MiMo) | ✅ |
|
| AI integration (DeepSeek, OpenAI, MiMo) | ✅ |
|
||||||
| Cross-compilation targets (JS, WASM, shared libs) | ✅ |
|
| Cross-compilation targets (JS, WASM, shared libs) | ✅ |
|
||||||
| Self-hosted REPL with tree-walking interpreter | ✅ |
|
| Self-hosted REPL with tree-walking interpreter | ✅ |
|
||||||
|
| First-class functions (defn as values) | ✅ |
|
||||||
|
| Multi-arity `defn` | ✅ |
|
||||||
|
| `&` rest parameters | ✅ |
|
||||||
|
| Keyword-as-function `(:key map)` | ✅ |
|
||||||
|
| `--lib-path` CLI flag + `CLJNIM_LIB_PATH` | ✅ |
|
||||||
| 276+ unit tests | ✅ |
|
| 276+ unit tests | ✅ |
|
||||||
| [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) compliance | ✅ |
|
| [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) compliance | ✅ |
|
||||||
|
|
||||||
@@ -192,6 +198,12 @@ make build
|
|||||||
./cljnim --help
|
./cljnim --help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Custom Library Path
|
||||||
|
Use `--lib-path` to point to your project's Nim runtime libraries:
|
||||||
|
```bash
|
||||||
|
./cljnim --lib-path ./my_project/lib run app.clj
|
||||||
|
```
|
||||||
|
|
||||||
## 🏗️ Architecture
|
## 🏗️ Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -113,6 +113,26 @@ These tasks are **small, well-defined, and high impact**:
|
|||||||
| T10.6 | Reader edge cases | ✅ | 🟢 | `src/reader.nim`, `tests/test_reader.nim` | `-.5`, `+.25`, `1e5`, `1.5e-3`, `-.5e-3`, `.25`. Scientific notation. 13 tests. |
|
| T10.6 | Reader edge cases | ✅ | 🟢 | `src/reader.nim`, `tests/test_reader.nim` | `-.5`, `+.25`, `1e5`, `1.5e-3`, `-.5e-3`, `.25`. Scientific notation. 13 tests. |
|
||||||
| T10.7 | CljVal type unification | ✅ | 🔴 | `src/types.nim`, `lib/cljnim_runtime.nim`, `src/runtime.nim`, `lib/cljnim_runtime_js.nim` | Identical CljKind enum (15 values) across all 4 files. All case statements cover new variants. |
|
| T10.7 | CljVal type unification | ✅ | 🔴 | `src/types.nim`, `lib/cljnim_runtime.nim`, `src/runtime.nim`, `lib/cljnim_runtime_js.nim` | Identical CljKind enum (15 values) across all 4 files. All case statements cover new variants. |
|
||||||
|
|
||||||
|
## Phase 11: BRing Compiler Fixes (12 May 2026)
|
||||||
|
|
||||||
|
Real-world fixes discovered during BRing web framework development.
|
||||||
|
|
||||||
|
| ID | Task | Status | Complexity | Files | Acceptance Criteria |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| T11.1 | Docstrings in `defn`/`defn-` | ✅ | 🟢 | `src/emitter.nim` | `(defn f "doc" [x] ...)` compiles correctly. |
|
||||||
|
| T11.2 | Multi-arity `defn` | ✅ | 🟡 | `src/emitter.nim` | `(defn f ([x] ...) ([x y] ...))` dispatches by `args.len`. |
|
||||||
|
| T11.3 | Keyword-as-function `(:key map)` | ✅ | 🟢 | `src/emitter.nim` | `(:name person)` emits `cljGet(person, keyword)` instead of `cljApply`. |
|
||||||
|
| T11.4 | `&` rest parameters | ✅ | 🟡 | `src/emitter.nim` | `(defn f [x & rest] ...)` accepts variadic args as `seq[CljVal]`. |
|
||||||
|
| T11.5 | Nim interop name mangling | ✅ | 🟢 | `src/emitter.nim` | `nim/foo-bar` emits `foo_bar` in Nim. `sanitizeNimIdent` for interop chains. |
|
||||||
|
| T11.6 | `getLibPath()` + `--lib-path` flag | ✅ | 🟢 | `src/cljnim.nim`, `src/repl.nim` | `--lib-path <dir>` CLI flag. Checks CLI → env → cwd → appDir. |
|
||||||
|
| T11.7 | `:paths` in `deps.edn` | ✅ | 🟢 | `src/deps.nim` | Parses `:paths` and includes in `searchPaths`. |
|
||||||
|
| T11.8 | `loop` + `if/else` discard fix | ✅ | 🟡 | `src/emitter.nim` | `loopResult` variable. `loop` returns value correctly in all branches. |
|
||||||
|
| T11.9 | `clj_` prefix for identifiers | ✅ | 🟢 | `src/emitter.nim` | All generated procs get `clj_` prefix. Nim interop excluded. |
|
||||||
|
| T11.10 | `when isMainModule` skip for libs | ✅ | 🟢 | `src/emitter.nim` | `emitProgramLib` skips `when isMainModule` guard. |
|
||||||
|
| T11.11 | `try`/`catch`/`finally` fixes | ✅ | 🟢 | `src/emitter.nim` | `finally` discards expression results. `catch` uses original name for scope. |
|
||||||
|
| T11.12 | First-class `defn` functions | ✅ | 🟡 | `src/emitter.nim` | `defn` symbols used as values emit `cljFn(...)` wrapper. Direct calls unchanged. |
|
||||||
|
| T11.13 | `loop` in expression context (IIFE) | ✅ | 🟡 | `src/emitter.nim` | `loop` wraps in `(proc(): CljVal = ...)()` when `needsValue=true`. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## How to claim a task
|
## How to claim a task
|
||||||
|
|||||||
@@ -33,6 +33,21 @@ make build
|
|||||||
```
|
```
|
||||||
Компилира до Nim, после до C, после до бинарен файл, и го изпълнява.
|
Компилира до Nim, после до C, после до бинарен файл, и го изпълнява.
|
||||||
|
|
||||||
|
### Глобални Флагове
|
||||||
|
|
||||||
|
#### `--lib-path <dir>` — Потребителска Директория за Библиотеки
|
||||||
|
Презаписва стандартния път за търсене на `lib/`. Проверява се преди `CLJNIM_LIB_PATH` environment променлива и вградените пътища.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cljnim --lib-path ./my_project/lib run app.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
Алтернатива чрез environment променлива:
|
||||||
|
```bash
|
||||||
|
export CLJNIM_LIB_PATH=./my_project/lib
|
||||||
|
./cljnim run app.clj
|
||||||
|
```
|
||||||
|
|
||||||
### `read` — Парсиране и Отпечатване на AST
|
### `read` — Парсиране и Отпечатване на AST
|
||||||
```bash
|
```bash
|
||||||
./cljnim read examples/hello.clj
|
./cljnim read examples/hello.clj
|
||||||
@@ -61,8 +76,24 @@ make build
|
|||||||
(defn greet [name]
|
(defn greet [name]
|
||||||
(println "Здравей, " name))
|
(println "Здравей, " name))
|
||||||
|
|
||||||
|
; Функция с docstring
|
||||||
|
(defn greet "Поздравява някого" [name]
|
||||||
|
(str "Здравей, " name))
|
||||||
|
|
||||||
|
; Multi-arity функция
|
||||||
|
(defn greet-multi
|
||||||
|
([name] (greet-multi name "Здравей"))
|
||||||
|
([name greeting] (str greeting ", " name)))
|
||||||
|
|
||||||
|
; Функция с rest параметри
|
||||||
|
(defn sum-all [x & rest]
|
||||||
|
(+ x (reduce + rest)))
|
||||||
|
|
||||||
; Извикване на функция
|
; Извикване на функция
|
||||||
(greet "Свят")
|
(greet "Свят")
|
||||||
|
(greet-multi "Алиса") ;; => "Здравей, Алиса"
|
||||||
|
(greet-multi "Алиса" "Здрасти") ;; => "Здрасти, Алиса"
|
||||||
|
(sum-all 1 2 3 4) ;; => 10
|
||||||
|
|
||||||
; Аритметика
|
; Аритметика
|
||||||
(+ 1 2 3) ; => 6
|
(+ 1 2 3) ; => 6
|
||||||
@@ -88,6 +119,10 @@ make build
|
|||||||
; Ключови думи
|
; Ключови думи
|
||||||
(def person {:name "Алиса" :age 30})
|
(def person {:name "Алиса" :age 30})
|
||||||
|
|
||||||
|
; Ключова дума като функция (търсене в карта)
|
||||||
|
(:name person) ;; => "Алиса"
|
||||||
|
(:age person) ;; => 30
|
||||||
|
|
||||||
; Картите и множествата използват persistent HAMT структури
|
; Картите и множествата използват persistent HAMT структури
|
||||||
; със structural sharing и O(log₃₂ n) операции.
|
; със structural sharing и O(log₃₂ n) операции.
|
||||||
```
|
```
|
||||||
@@ -102,6 +137,33 @@ make build
|
|||||||
(println (factorial 5)) ; => 120
|
(println (factorial 5)) ; => 120
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Функции от Първи Клас
|
||||||
|
Потребителските функции могат да се подават като стойности на функции от по-висок ред:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn square [x] (* x x))
|
||||||
|
(defn odd? [x] (= 1 (mod x 2)))
|
||||||
|
|
||||||
|
(map square [1 2 3 4]) ;; => [1 4 9 16]
|
||||||
|
(filter odd? [1 2 3 4]) ;; => [1 3]
|
||||||
|
(apply + [1 2 3]) ;; => 6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loop / Recur
|
||||||
|
```clojure
|
||||||
|
(defn find-header [headers name]
|
||||||
|
(loop [pairs (seq headers)]
|
||||||
|
(if (empty? pairs)
|
||||||
|
nil
|
||||||
|
(let [[k v] (first pairs)]
|
||||||
|
(if (= k name)
|
||||||
|
v
|
||||||
|
(recur (rest pairs)))))))
|
||||||
|
|
||||||
|
(find-header [[:content-type "text/html"] [:accept "*/*"]] :accept)
|
||||||
|
;; => "*/*"
|
||||||
|
```
|
||||||
|
|
||||||
## Ръководство за AI REPL
|
## Ръководство за AI REPL
|
||||||
|
|
||||||
JSON REPL е проектиран за програмно взаимодействие.
|
JSON REPL е проектиран за програмно взаимодействие.
|
||||||
|
|||||||
@@ -33,6 +33,21 @@ Generates a `.nim` file from Clojure source.
|
|||||||
```
|
```
|
||||||
Compiles to Nim, then to C, then to a binary, and runs it.
|
Compiles to Nim, then to C, then to a binary, and runs it.
|
||||||
|
|
||||||
|
### Global Flags
|
||||||
|
|
||||||
|
#### `--lib-path <dir>` — Custom Library Directory
|
||||||
|
Override the default `lib/` search path. Checked before `CLJNIM_LIB_PATH` env var and built-in paths.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cljnim --lib-path ./my_project/lib run app.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
Environment variable alternative:
|
||||||
|
```bash
|
||||||
|
export CLJNIM_LIB_PATH=./my_project/lib
|
||||||
|
./cljnim run app.clj
|
||||||
|
```
|
||||||
|
|
||||||
### `read` — Parse and Print AST
|
### `read` — Parse and Print AST
|
||||||
```bash
|
```bash
|
||||||
./cljnim read examples/hello.clj
|
./cljnim read examples/hello.clj
|
||||||
@@ -61,8 +76,24 @@ Shows the Clojure AST as S-expressions.
|
|||||||
(defn greet [name]
|
(defn greet [name]
|
||||||
(println "Hello, " name))
|
(println "Hello, " name))
|
||||||
|
|
||||||
|
; Function with docstring
|
||||||
|
(defn greet "Says hello to someone" [name]
|
||||||
|
(str "Hello, " name))
|
||||||
|
|
||||||
|
; Multi-arity function
|
||||||
|
(defn greet-multi
|
||||||
|
([name] (greet-multi name "Hello"))
|
||||||
|
([name greeting] (str greeting ", " name)))
|
||||||
|
|
||||||
|
; Function with rest parameters
|
||||||
|
(defn sum-all [x & rest]
|
||||||
|
(+ x (reduce + rest)))
|
||||||
|
|
||||||
; Call a function
|
; Call a function
|
||||||
(greet "World")
|
(greet "World")
|
||||||
|
(greet-multi "Alice") ;; => "Hello, Alice"
|
||||||
|
(greet-multi "Alice" "Hi") ;; => "Hi, Alice"
|
||||||
|
(sum-all 1 2 3 4) ;; => 10
|
||||||
|
|
||||||
; Arithmetic
|
; Arithmetic
|
||||||
(+ 1 2 3) ; => 6
|
(+ 1 2 3) ; => 6
|
||||||
@@ -88,6 +119,10 @@ Shows the Clojure AST as S-expressions.
|
|||||||
; Keywords
|
; Keywords
|
||||||
(def person {:name "Alice" :age 30})
|
(def person {:name "Alice" :age 30})
|
||||||
|
|
||||||
|
; Keyword as function (lookup in map)
|
||||||
|
(:name person) ;; => "Alice"
|
||||||
|
(:age person) ;; => 30
|
||||||
|
|
||||||
; Maps and sets use persistent HAMT data structures
|
; Maps and sets use persistent HAMT data structures
|
||||||
; with structural sharing and O(log₃₂ n) operations.
|
; with structural sharing and O(log₃₂ n) operations.
|
||||||
```
|
```
|
||||||
@@ -102,6 +137,33 @@ Shows the Clojure AST as S-expressions.
|
|||||||
(println (factorial 5)) ; => 120
|
(println (factorial 5)) ; => 120
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### First-Class Functions
|
||||||
|
User-defined functions can be passed as values to higher-order functions:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(defn square [x] (* x x))
|
||||||
|
(defn odd? [x] (= 1 (mod x 2)))
|
||||||
|
|
||||||
|
(map square [1 2 3 4]) ;; => [1 4 9 16]
|
||||||
|
(filter odd? [1 2 3 4]) ;; => [1 3]
|
||||||
|
(apply + [1 2 3]) ;; => 6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loop / Recur
|
||||||
|
```clojure
|
||||||
|
(defn find-header [headers name]
|
||||||
|
(loop [pairs (seq headers)]
|
||||||
|
(if (empty? pairs)
|
||||||
|
nil
|
||||||
|
(let [[k v] (first pairs)]
|
||||||
|
(if (= k name)
|
||||||
|
v
|
||||||
|
(recur (rest pairs)))))))
|
||||||
|
|
||||||
|
(find-header [[:content-type "text/html"] [:accept "*/*"]] :accept)
|
||||||
|
;; => "*/*"
|
||||||
|
```
|
||||||
|
|
||||||
## AI REPL Guide
|
## AI REPL Guide
|
||||||
|
|
||||||
The JSON REPL is designed for programmatic interaction.
|
The JSON REPL is designed for programmatic interaction.
|
||||||
|
|||||||
Reference in New Issue
Block a user