bc33eab7fc
Documented 10+ compiler limitations and workarounds discovered during BRing development.
246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
# Препоръки към 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.*
|