Files
bara-lang/CLJNIM_RECOMMENDATIONS.md
dimgigov 7adace4ee1 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
2026-05-12 14:10:31 +03:00

288 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Препоръки към cljnim компилатора
> Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.
>
> **Последно обновяване:** 12 май 2026
---
## 1. Поддръжка на docstrings в `defn` ✅ Имплементирано
**Проблем (преди):**
```clojure
(defn greet "Says hello" [name]
(str "Hello " name))
```
Гърмеше с:
```
Error: unhandled exception: defn params must be a vector [EmitterError]
```
**Решение:** В `emitSpecialForm` за `defn` сега се проверява дали `items[2]` е стринг (docstring) и ако да — се пропуска и се взема `items[3]` като параметри.
**Пример (сега работи):**
```clojure
(defn greet "Says hello" [name]
(str "Hello " name))
```
---
## 2. Поддръжка на multi-arity `defn` ✅ Имплементирано
**Проблем (преди):**
```clojure
(defn greet
([name] (greet name "Hello"))
([name greeting] (str greeting " " name)))
```
Гърмеше с:
```
Error: unhandled exception: defn params must be a vector [EmitterError]
```
**Решение:** Multi-arity синтаксисът сега се разпознава. Генерира се dispatch по `args.len` в Nim.
**Пример (сега работи):**
```clojure
(defn greet
([name] (greet name "Hello"))
([name greeting] (str greeting " " name)))
(greet "Alice") ;; => "Hello Alice"
(greet "Alice" "Hi") ;; => "Hi Alice"
```
---
## 3. `(:key map)` синтаксис (keyword като функция) ✅ Имплементирано
**Проблем (преди):**
```clojure
(:request-method req) ; Clojure стандарт
```
Генерираше `cljApply(...)` вместо `cljGet(...)` и гърмеше с:
```
apply requires a function
```
**Решение:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, сега се генерира `cljGet(args[0], keyword)` вместо `cljApply`.
**Пример (сега работи):**
```clojure
(def req {:request-method :get :uri "/hello"})
(:request-method req) ;; => :get
(:uri req) ;; => "/hello"
```
---
## 4. `&` rest параметри в `defn` ✅ Имплементирано
**Проблем (преди):**
```clojure
(defn greet [name & [greeting]]
...)
```
Гърмеше с:
```
Error: unhandled exception: defn params must be symbols [EmitterError]
```
**Решение:** `&` сега се разпознава като специален символ в параметрите. Генерира се Nim код, който приема `seq[CljVal]` и разпределя аргументите.
**Пример (сега работи):**
```clojure
(defn sum-all [x & rest]
(+ x (reduce + rest)))
(sum-all 1 2 3 4) ;; => 10
```
---
## 5. Nim interop — mangle на имената ✅ Имплементирано
**Проблем (преди):**
```clojure
(nim/bring_http/run-server handler port)
```
Генерираше:
```nim
run-server(handler, port)
```
А `-` не е валиден символ в Nim идентификатори.
**Решение:** В `emitSpecialForm` за `nim/` interop се прилага `sanitizeNimIdent()`, която заменя `-` с `_` и премахва други невалидни символи за Nim.
**Пример (сега работи):**
```clojure
(nim/bring_http/run_server handler port) ;; run_server в Nim
(nim/strutils/to_upper "hello") ;; to_upper в Nim
```
---
## 6. `getLibPath()` — търсене на `lib/` директория ✅ Имплементирано
**Проблем (преди):** `getLibPath()` винаги връщаше `appDir/lib`, което правеше невъзможно проекти от други директории да използват свои `lib/` пътища.
**Решение:**
- Добавен `--lib-path <dir>` глобален CLI флаг.
- `getLibPath()` сега проверява в следния ред:
1. CLI `--lib-path` override
2. `CLJNIM_LIB_PATH` environment variable
3. Текуща директория `lib/`
4. Приложение `lib/`
**Пример:**
```bash
# Със собствена 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` ✅ Имплементирано
**Проблем (преди):** `deps.nim` парсираше само `:deps` от `deps.edn`, но не и `:paths`.
**Решение:** Добавен е парсинг на `:paths` от `deps.edn` и се включват в `searchPaths` при компилация.
**Пример (сега работи):**
```clojure
;; deps.edn
{:paths ["src" "lib"]
:deps {org.clojure/core.async {:mvn/version "1.6.681"}}}
```
---
## 8. `loop` с `if/else` — проблеми с `discard` ✅ Имплементирано
**Проблем (преди):**
```clojure
(loop [pairs (seq headers)]
(if (empty? pairs)
nil
(let [[k v] (first pairs)]
(if (= k name)
v
(recur (rest pairs))))))
```
Гърмеше с:
```
expression 'cljNil()' is of type 'CljVal' and has to be used (or discarded)
```
**Решение:**
- Добавен е `loopResult` променлива, която събира резултата.
- Подобрено е генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност правилно.
- Добавена е `needsValue` логика — `loop` се wrap-ва в IIFE `(proc(): CljVal = ...)()` когато е в expression context (напр. като аргумент на функция, в `let` RHS, или като последна стойност в програма).
**Пример (сега работи):**
```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)
;; => "*/*"
```
---
## 9. Конфликти на имена между Clojure и Nim ✅ Имплементирано
**Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи:
- `parseQueryString``parse_query_string``parsequerystring`
Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име.
**Решение:** Добавен е `clj_` prefix към всички генерирани Nim процедури от Clojure код:
```nim
proc clj_parse_query_string(...) ; вместо parse_query_string
```
За `nim/` interop се използва `sanitizeNimIdent()` без prefix, така че native Nim имената да не се чупят.
---
## 10. `when isMainModule` в генерирания код ✅ Имплементирано
**Проблем (преди):** `when isMainModule` в края на генерирания Nim файл означаваше, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни.
**Решение:** `emitProgramLib` сега skip-ва `when isMainModule` guard, което позволява генерирането на "библиотечни" Clojure файлове.
---
## 11. Поддръжка на `try`/`catch`/`finally` ✅ Имплементирано
**Решение:**
- `finally` блок сега правилно discard-ва expression резултатите.
- `catch` handler използва оригиналното име за scope tracking.
**Пример (сега работи):**
```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` | ✅ Готово | e36bf4e |
| 2 | Multi-arity `defn` | ✅ Готово | e36bf4e |
| 3 | `(:key map)` синтаксис | ✅ Готово | e36bf4e |
| 4 | `&` rest параметри | ✅ Готово | e36bf4e |
| 5 | Nim interop mangle | ✅ Готово | e36bf4e, 3aaf3c0 |
| 6 | `getLibPath()` / `--lib-path` | ✅ Готово | e36bf4e, 9a8519d |
| 7 | `:paths` в deps.edn | ✅ Готово | e36bf4e |
| 8 | `loop` + `if/else` discard | ✅ Готово | e36bf4e, a1a65f1 |
| 9 | Конфликти на имена (`clj_` prefix) | ✅ Готово | fb8928e, 3aaf3c0 |
| 10 | `when isMainModule` | ✅ Готово | e36bf4e |
| 11 | `try`/`catch`/`finally` | ✅ Готово | fb8928e |
| 12 | First-class `defn` функции | ✅ Готово | aac39a0 |
---
*Документът е съставен по време на разработката на [BRing](https://github.com/) — нативна Clojure уеб библиотека за cljnim.*
*Последно обновяване: 12 май 2026*