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

10 KiB
Raw Blame History

Препоръки към cljnim компилатора

Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.


1. Поддръжка на docstrings в defn

Проблем:

(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

Проблем:

(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:

(defn greet-default [name]
  (greet name "Hello"))

(defn greet [name greeting]
  (str greeting " " name))

3. (:key map) синтаксис (keyword като функция)

Проблем:

(:request-method req)   ; Clojure стандарт

Генерира:

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 за сега: Пишете явно:

(get req :request-method)

4. & rest параметри в defn

Проблем:

(defn greet [name & [greeting]]
  ...)

Гърми с:

Error: unhandled exception: defn params must be symbols [EmitterError]

Причина: & не се разпознава като специален символ в параметрите.

Препоръка: Да се добави поддръжка за variadic параметри чрез &. Може да се генерира Nim код, който приема seq[CljVal] и разпределя аргументите.

Workaround за сега: Използвайте фиксиран брой аргументи или подавайте колекции (вектори/map-ове) като един аргумент.


5. Nim interop — mangle на имената

Проблем:

(nim/bring_http/run-server handler port)

Генерира:

run-server(handler, port)

Но - не е валиден символ в Nim идентификатори. Гърми при компилация на Nim.

Причина: В emitSpecialForm за nim/ interop:

var funcChain = parts[2..^1].join(".")

Името се предава директно без mangle.

Препоръка: Да се приложи mangleName() или подобна функция върху funcChain, която заменя - с _ и премахва други невалидни символи за Nim.

Workaround за сега: В Nim модулите използвайте само camelCase или snake_case имена без тирета.


6. getLibPath() — търсене на lib/ директория

Проблем:

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/:

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.

Причина:

proc parseDepsMap(depsMap: CljVal): seq[Dependency] =
  # Парсира само :deps

Препоръка: Да се добави парсинг на :paths и да се включват в searchPaths при компилация.

Workaround за сега: Clojure файловете трябва да са в структура, която съвпада с namespace имената, относително към текущата директория.


8. loop с if/else — проблеми с discard

Проблем:

(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 за първата буква и игнорира _. Значи:

  • parseQueryStringparse_query_stringparsequerystring

Ако cljnim генерира proc parse_query_string и съществува proc parseQueryString в импортиран модул, Nim ги третира като едно и също име. Това води до:

  • Презаписване на функцията
  • Безкрайна рекурсия, ако едната извиква другата

Препоръка: Да се добави prefix към всички генерирани Nim процедури, например clj_:

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 — нативна Clojure уеб библиотека за cljnim.