diff --git a/CLJNIM_RECOMMENDATIONS.md b/CLJNIM_RECOMMENDATIONS.md new file mode 100644 index 0000000..4420c2e --- /dev/null +++ b/CLJNIM_RECOMMENDATIONS.md @@ -0,0 +1,245 @@ +# Препоръки към 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.*