Files
bara-lang/CLJNIM_RECOMMENDATIONS.md
T
dimgigov bc33eab7fc docs: add CLJNIM_RECOMMENDATIONS.md (Bulgarian)
Documented 10+ compiler limitations and workarounds discovered
during BRing development.
2026-05-12 11:16:47 +03:00

246 lines
10 KiB
Markdown
Raw 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.
---
## 1. Поддръжка на docstrings в `defn`
**Проблем:**
```clojure
(defn greet "Says hello" [name]
(str "Hello " name))
```
Гърми с:
```
Error: unhandled exception: defn params must be a vector [EmitterError]
```
**Причина:** `defn` в `emitter.nim` очаква вторият аргумент да е вектор с параметри. Docstring-ът се третира като име на параметър.
**Препоръка:** В `emitSpecialForm` за `defn` да се провери дали `items[2]` е стринг (docstring) и ако да — да се пропусне и да се вземе `items[3]` като параметри.
**Workaround за сега:** Не използвайте docstrings в `defn`. Пишете ги като обикновени коментари `;` преди функцията.
---
## 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 синтаксисът не се разпознава. `items[2]` е списък `([name] ...)`, а не вектор.
**Препоръка:** Да се добави логика в `emitSpecialForm` за `defn`, която проверява дали `items[2]` е списък от arity тела. Ако да — да се генерира Nim overload или dispatch по `args.len`.
**Workaround за сега:** Използвайте отделни функции за всяка arity:
```clojure
(defn greet-default [name]
(greet name "Hello"))
(defn greet [name greeting]
(str greeting " " name))
```
---
## 3. `(:key map)` синтаксис (keyword като функция)
**Проблем:**
```clojure
(:request-method req) ; Clojure стандарт
```
Генерира:
```nim
cljApply(cljKeyword("request-method"), cljList(@[req]))
```
А `cljApply` изисква първият аргумент да е `ckFn` и гърми с:
```
apply requires a function
```
**Причина:** Когато първият елемент на списък е `ckKeyword`, `emitSpecialForm` го третира като "user-defined function call" и генерира `cljApply` вместо `cljGet`.
**Препоръка:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, да се генерира `cljGet(args[0], keyword)` вместо `cljApply`.
**Workaround за сега:** Пишете явно:
```clojure
(get req :request-method)
```
---
## 4. `&` rest параметри в `defn`
**Проблем:**
```clojure
(defn greet [name & [greeting]]
...)
```
Гърми с:
```
Error: unhandled exception: defn params must be symbols [EmitterError]
```
**Причина:** `&` не се разпознава като специален символ в параметрите.
**Препоръка:** Да се добави поддръжка за variadic параметри чрез `&`. Може да се генерира Nim код, който приема `seq[CljVal]` и разпределя аргументите.
**Workaround за сега:** Използвайте фиксиран брой аргументи или подавайте колекции (вектори/map-ове) като един аргумент.
---
## 5. Nim interop — mangle на имената
**Проблем:**
```clojure
(nim/bring_http/run-server handler port)
```
Генерира:
```nim
run-server(handler, port)
```
Но `-` не е валиден символ в Nim идентификатори. Гърми при компилация на Nim.
**Причина:** В `emitSpecialForm` за `nim/` interop:
```nim
var funcChain = parts[2..^1].join(".")
```
Името се предава директно без mangle.
**Препоръка:** Да се приложи `mangleName()` или подобна функция върху `funcChain`, която заменя `-` с `_` и премахва други невалидни символи за Nim.
**Workaround за сега:** В Nim модулите използвайте само `camelCase` или `snake_case` имена без тирета.
---
## 6. `getLibPath()` — търсене на `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/` пътища.
**Препоръка:** Да се поддържа `CLJNIM_LIB_PATH` environment variable или `--lib-path` CLI флаг. Алтернативно, `candidate3` (текущата директория) да се проверява първи, преди `appDir`.
**Workaround за сега:** Символна връзка на Nim модулите в `cljnim/lib/`:
```bash
ln -s /path/to/project/lib/my_module.nim /home/ziko/lispnim/lib/my_module.nim
```
---
## 7. Поддръжка на `:paths` в `deps.edn`
**Проблем:** `deps.nim` парсира само `:deps` от `deps.edn`, но не и `:paths`.
**Причина:**
```nim
proc parseDepsMap(depsMap: CljVal): seq[Dependency] =
# Парсира само :deps
```
**Препоръка:** Да се добави парсинг на `:paths` и да се включват в `searchPaths` при компилация.
**Workaround за сега:** Clojure файловете трябва да са в структура, която съвпада с namespace имената, относително към текущата директория.
---
## 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))))))
```
Генерира код, в който `cljNil()` в `else` клона не е discard-нат правилно и Nim гърми с:
```
expression 'cljNil()' is of type 'CljVal' and has to be used (or discarded)
```
**Причина:** `emitSpecialForm` за `loop` добавя `break` след тялото, но `if/else` вътре в `while true:` блок не винаги връща стойност във всички клонове.
**Препоръка:** Да се подобри генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност или да се използва `result = ...` pattern.
**Workaround за сега:** Използвайте `filter` + `first` вместо ръчен `loop`/`recur`.
---
## 9. Конфликти на имена между Clojure и Nim
**Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи:
- `parseQueryString``parse_query_string``parsequerystring`
Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име. Това води до:
- Презаписване на функцията
- Безкрайна рекурсия, ако едната извиква другата
**Препоръка:** Да се добави prefix към всички генерирани Nim процедури, например `clj_`:
```nim
proc clj_parse_query_string(...) ; вместо parse_query_string
```
**Workaround за сега:** В Nim runtime модулите използвайте уникални имена с префикс (напр. `bringParseQueryString`).
---
## 10. `when isMainModule` в генерирания код
**Проблем:** `when isMainModule` в края на генерирания Nim файл означава, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни. Това прави невъзможно създаването на "библиотечни" Clojure файлове, които съдържат и дефиниции, и изпълним код.
**Препоръка:** Да се добави флаг `--lib` или подобен, който да генерира код без `when isMainModule` за библиотечни файлове.
**Workaround за сега:** Използвайте `cljnim compile file.clj output.nim` и после ръчно `nim c output.nim`.
---
## 11. Поддръжка на `try`/`catch`/`finally`
**Статус:** Изглежда се поддържа, но не беше тествана обстойно по време на разработката на BRing.
**Препоръка:** Да се добавят тестове за exception handling в HTTP контекст (грешки при парсиране, мрежови грешки).
---
## Обобщена таблица
| # | Проблем | Трудност | Приоритет |
|---|---------|----------|-----------|
| 1 | Docstrings в `defn` | Лесно | Висок |
| 2 | Multi-arity `defn` | Средно | Висок |
| 3 | `(:key map)` синтаксис | Лесно | Висок |
| 4 | `&` rest параметри | Средно | Среден |
| 5 | Nim interop mangle | Лесно | Висок |
| 6 | `getLibPath()` | Лесно | Среден |
| 7 | `:paths` в deps.edn | Средно | Среден |
| 8 | `loop` + `if/else` | Средно | Среден |
| 9 | Конфликти на имена | Средно | Висок |
| 10 | `when isMainModule` | Лесно | Нисък |
---
*Документът е съставен по време на разработката на [BRing](https://github.com/) — нативна Clojure уеб библиотека за cljnim.*