Documented 10+ compiler limitations and workarounds discovered during BRing development.
10 KiB
Препоръки към 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 за първата буква и игнорира _. Значи:
parseQueryString≡parse_query_string≡parsequerystring
Ако 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.