- CLJNIM_RECOMMENDATIONS.md: mark all 12 recommendations as implemented - README.md: add --lib-path, first-class functions, multi-arity, rest params - User guides (EN/BG): add sections for docstrings, multi-arity, rest params, keyword-as-function, first-class functions, loop/recur, --lib-path flag - TASKS.md: add Phase 11 with all BRing compiler fixes
10 KiB
Препоръки към cljnim компилатора
Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.
Последно обновяване: 12 май 2026
1. Поддръжка на docstrings в defn ✅ Имплементирано
Проблем (преди):
(defn greet "Says hello" [name]
(str "Hello " name))
Гърмеше с:
Error: unhandled exception: defn params must be a vector [EmitterError]
Решение: В emitSpecialForm за defn сега се проверява дали items[2] е стринг (docstring) и ако да — се пропуска и се взема items[3] като параметри.
Пример (сега работи):
(defn greet "Says hello" [name]
(str "Hello " name))
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 синтаксисът сега се разпознава. Генерира се dispatch по args.len в Nim.
Пример (сега работи):
(defn greet
([name] (greet name "Hello"))
([name greeting] (str greeting " " name)))
(greet "Alice") ;; => "Hello Alice"
(greet "Alice" "Hi") ;; => "Hi Alice"
3. (:key map) синтаксис (keyword като функция) ✅ Имплементирано
Проблем (преди):
(:request-method req) ; Clojure стандарт
Генерираше cljApply(...) вместо cljGet(...) и гърмеше с:
apply requires a function
Решение: В emitSpecialForm, когато items[0].kind == ckKeyword, сега се генерира cljGet(args[0], keyword) вместо cljApply.
Пример (сега работи):
(def req {:request-method :get :uri "/hello"})
(:request-method req) ;; => :get
(:uri req) ;; => "/hello"
4. & rest параметри в defn ✅ Имплементирано
Проблем (преди):
(defn greet [name & [greeting]]
...)
Гърмеше с:
Error: unhandled exception: defn params must be symbols [EmitterError]
Решение: & сега се разпознава като специален символ в параметрите. Генерира се Nim код, който приема seq[CljVal] и разпределя аргументите.
Пример (сега работи):
(defn sum-all [x & rest]
(+ x (reduce + rest)))
(sum-all 1 2 3 4) ;; => 10
5. Nim interop — mangle на имената ✅ Имплементирано
Проблем (преди):
(nim/bring_http/run-server handler port)
Генерираше:
run-server(handler, port)
А - не е валиден символ в Nim идентификатори.
Решение: В emitSpecialForm за nim/ interop се прилага sanitizeNimIdent(), която заменя - с _ и премахва други невалидни символи за Nim.
Пример (сега работи):
(nim/bring_http/run_server handler port) ;; run_server в Nim
(nim/strutils/to_upper "hello") ;; to_upper в Nim
6. getLibPath() — търсене на lib/ директория ✅ Имплементирано
Проблем (преди): getLibPath() винаги връщаше appDir/lib, което правеше невъзможно проекти от други директории да използват свои lib/ пътища.
Решение:
- Добавен
--lib-path <dir>глобален CLI флаг. getLibPath()сега проверява в следния ред:- CLI
--lib-pathoverride CLJNIM_LIB_PATHenvironment variable- Текуща директория
lib/ - Приложение
lib/
- CLI
Пример:
# Със собствена lib директория
./cljnim --lib-path ./my_project/lib run app.clj
# С environment variable
export CLJNIM_LIB_PATH=./my_project/lib
./cljnim run app.clj
7. Поддръжка на :paths в deps.edn ✅ Имплементирано
Проблем (преди): deps.nim парсираше само :deps от deps.edn, но не и :paths.
Решение: Добавен е парсинг на :paths от deps.edn и се включват в searchPaths при компилация.
Пример (сега работи):
;; deps.edn
{:paths ["src" "lib"]
:deps {org.clojure/core.async {:mvn/version "1.6.681"}}}
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))))))
Гърмеше с:
expression 'cljNil()' is of type 'CljVal' and has to be used (or discarded)
Решение:
- Добавен е
loopResultпроменлива, която събира резултата. - Подобрено е генерирането на
loopтака, че всички клонове наifвътре да връщат стойност правилно. - Добавена е
needsValueлогика —loopсе wrap-ва в IIFE(proc(): CljVal = ...)()когато е в expression context (напр. като аргумент на функция, вletRHS, или като последна стойност в програма).
Пример (сега работи):
(defn find-header [headers name]
(loop [pairs (seq headers)]
(if (empty? pairs)
nil
(let [[k v] (first pairs)]
(if (= k name)
v
(recur (rest pairs)))))))
(find-header [[:content-type "text/html"] [:accept "*/*"]] :accept)
;; => "*/*"
9. Конфликти на имена между Clojure и Nim ✅ Имплементирано
Проблем: Nim е case-insensitive за първата буква и игнорира _. Значи:
parseQueryString≡parse_query_string≡parsequerystring
Ако cljnim генерира proc parse_query_string и съществува proc parseQueryString в импортиран модул, Nim ги третира като едно и също име.
Решение: Добавен е clj_ prefix към всички генерирани Nim процедури от Clojure код:
proc clj_parse_query_string(...) ; вместо parse_query_string
За nim/ interop се използва sanitizeNimIdent() без prefix, така че native Nim имената да не се чупят.
10. when isMainModule в генерирания код ✅ Имплементирано
Проблем (преди): when isMainModule в края на генерирания Nim файл означаваше, че ако файлът се импортира от друг Nim модул, main кодът няма да се изпълни.
Решение: emitProgramLib сега skip-ва when isMainModule guard, което позволява генерирането на "библиотечни" Clojure файлове.
11. Поддръжка на try/catch/finally ✅ Имплементирано
Решение:
finallyблок сега правилно discard-ва expression резултатите.catchhandler използва оригиналното име за scope tracking.
Пример (сега работи):
(try
(risky-operation)
(catch Exception e
(println "Error:" (:message e)))
(finally
(cleanup)))
12. First-class функции (defn като стойности) ✅ Имплементирано
Проблем (преди): User-defined defn функции не можеха да се подават като стойности на други функции (напр. map, filter, apply).
Решение:
- Проследяват се арностите на функциите в
definedFnAritiesтаблица. - Когато символ на
defnсе използва като стойност, се генерираcljFnwrapper:- Multi-arity / rest:
cljFn(procName)(вече еseq[CljVal]) - Single-arity:
cljFn(proc(args): procName(args[0], ...))
- Multi-arity / rest:
- Директни извиквания остават непроменени (fast path).
Пример (сега работи):
(defn square [x] (* x x))
(map square [1 2 3 4]) ;; => [1 4 9 16]
(filter odd? [1 2 3 4]) ;; => [1 3]
(apply + [1 2 3]) ;; => 6
Обобщена таблица
| # | Проблем | Статус | Commit |
|---|---|---|---|
| 1 | Docstrings в defn |
✅ Готово | e36bf4e |
| 2 | Multi-arity defn |
✅ Готово | e36bf4e |
| 3 | (:key map) синтаксис |
✅ Готово | e36bf4e |
| 4 | & rest параметри |
✅ Готово | e36bf4e |
| 5 | Nim interop mangle | ✅ Готово | e36bf4e, 3aaf3c0 |
| 6 | getLibPath() / --lib-path |
✅ Готово | e36bf4e, 9a8519d |
| 7 | :paths в deps.edn |
✅ Готово | e36bf4e |
| 8 | loop + if/else discard |
✅ Готово | e36bf4e, a1a65f1 |
| 9 | Конфликти на имена (clj_ prefix) |
✅ Готово | fb8928e, 3aaf3c0 |
| 10 | when isMainModule |
✅ Готово | e36bf4e |
| 11 | try/catch/finally |
✅ Готово | fb8928e |
| 12 | First-class defn функции |
✅ Готово | aac39a0 |
Документът е съставен по време на разработката на BRing — нативна Clojure уеб библиотека за cljnim. Последно обновяване: 12 май 2026