Files
bara-lang/CLJNIM_RECOMMENDATIONS.md
dimgigov 7adace4ee1 docs: update documentation for May 12 compiler fixes
- 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
2026-05-12 14:10:31 +03:00

10 KiB
Raw Permalink Blame History

Препоръки към 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() сега проверява в следния ред:
    1. CLI --lib-path override
    2. CLJNIM_LIB_PATH environment variable
    3. Текуща директория lib/
    4. Приложение lib/

Пример:

# Със собствена 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 (напр. като аргумент на функция, в let RHS, или като последна стойност в програма).

Пример (сега работи):

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

  • parseQueryStringparse_query_stringparsequerystring

Ако 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 резултатите.
  • catch handler използва оригиналното име за 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 се използва като стойност, се генерира cljFn wrapper:
    • Multi-arity / rest: cljFn(procName) (вече е seq[CljVal])
    • Single-arity: cljFn(proc(args): procName(args[0], ...))
  • Директни извиквания остават непроменени (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