diff --git a/CLJNIM_RECOMMENDATIONS.md b/CLJNIM_RECOMMENDATIONS.md index 4420c2e..5c6ea7b 100644 --- a/CLJNIM_RECOMMENDATIONS.md +++ b/CLJNIM_RECOMMENDATIONS.md @@ -1,171 +1,170 @@ # Препоръки към cljnim компилатора > Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang. +> +> **Последно обновяване:** 12 май 2026 --- -## 1. Поддръжка на docstrings в `defn` +## 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]` като параметри. -**Препоръка:** В `emitSpecialForm` за `defn` да се провери дали `items[2]` е стринг (docstring) и ако да — да се пропусне и да се вземе `items[3]` като параметри. - -**Workaround за сега:** Не използвайте docstrings в `defn`. Пишете ги като обикновени коментари `;` преди функцията. +**Пример (сега работи):** +```clojure +(defn greet "Says hello" [name] + (str "Hello " name)) +``` --- -## 2. Поддръжка на multi-arity `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] ...)`, а не вектор. +**Решение:** Multi-arity синтаксисът сега се разпознава. Генерира се dispatch по `args.len` в Nim. -**Препоръка:** Да се добави логика в `emitSpecialForm` за `defn`, която проверява дали `items[2]` е списък от arity тела. Ако да — да се генерира Nim overload или dispatch по `args.len`. - -**Workaround за сега:** Използвайте отделни функции за всяка arity: +**Пример (сега работи):** ```clojure -(defn greet-default [name] - (greet name "Hello")) +(defn greet + ([name] (greet name "Hello")) + ([name greeting] (str greeting " " name))) -(defn greet [name greeting] - (str greeting " " name)) +(greet "Alice") ;; => "Hello Alice" +(greet "Alice" "Hi") ;; => "Hi Alice" ``` --- -## 3. `(:key map)` синтаксис (keyword като функция) +## 3. `(:key map)` синтаксис (keyword като функция) ✅ Имплементирано -**Проблем:** +**Проблем (преди):** ```clojure (:request-method req) ; Clojure стандарт ``` -Генерира: -```nim -cljApply(cljKeyword("request-method"), cljList(@[req])) -``` -А `cljApply` изисква първият аргумент да е `ckFn` и гърми с: +Генерираше `cljApply(...)` вместо `cljGet(...)` и гърмеше с: ``` apply requires a function ``` -**Причина:** Когато първият елемент на списък е `ckKeyword`, `emitSpecialForm` го третира като "user-defined function call" и генерира `cljApply` вместо `cljGet`. +**Решение:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, сега се генерира `cljGet(args[0], keyword)` вместо `cljApply`. -**Препоръка:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, да се генерира `cljGet(args[0], keyword)` вместо `cljApply`. - -**Workaround за сега:** Пишете явно: +**Пример (сега работи):** ```clojure -(get req :request-method) +(def req {:request-method :get :uri "/hello"}) +(:request-method req) ;; => :get +(:uri req) ;; => "/hello" ``` --- -## 4. `&` rest параметри в `defn` +## 4. `&` rest параметри в `defn` ✅ Имплементирано -**Проблем:** +**Проблем (преди):** ```clojure (defn greet [name & [greeting]] ...) ``` -Гърми с: +Гърмеше с: ``` Error: unhandled exception: defn params must be symbols [EmitterError] ``` -**Причина:** `&` не се разпознава като специален символ в параметрите. +**Решение:** `&` сега се разпознава като специален символ в параметрите. Генерира се Nim код, който приема `seq[CljVal]` и разпределя аргументите. -**Препоръка:** Да се добави поддръжка за variadic параметри чрез `&`. Може да се генерира Nim код, който приема `seq[CljVal]` и разпределя аргументите. +**Пример (сега работи):** +```clojure +(defn sum-all [x & rest] + (+ x (reduce + rest))) -**Workaround за сега:** Използвайте фиксиран брой аргументи или подавайте колекции (вектори/map-ове) като един аргумент. +(sum-all 1 2 3 4) ;; => 10 +``` --- -## 5. Nim interop — mangle на имената +## 5. Nim interop — mangle на имената ✅ Имплементирано -**Проблем:** +**Проблем (преди):** ```clojure (nim/bring_http/run-server handler port) ``` -Генерира: +Генерираше: ```nim run-server(handler, port) ``` -Но `-` не е валиден символ в Nim идентификатори. Гърми при компилация на Nim. +А `-` не е валиден символ в Nim идентификатори. -**Причина:** В `emitSpecialForm` за `nim/` interop: -```nim -var funcChain = parts[2..^1].join(".") +**Решение:** В `emitSpecialForm` за `nim/` interop се прилага `sanitizeNimIdent()`, която заменя `-` с `_` и премахва други невалидни символи за Nim. + +**Пример (сега работи):** +```clojure +(nim/bring_http/run_server handler port) ;; run_server в Nim +(nim/strutils/to_upper "hello") ;; to_upper в Nim ``` -Името се предава директно без mangle. - -**Препоръка:** Да се приложи `mangleName()` или подобна функция върху `funcChain`, която заменя `-` с `_` и премахва други невалидни символи за Nim. - -**Workaround за сега:** В Nim модулите използвайте само `camelCase` или `snake_case` имена без тирета. --- -## 6. `getLibPath()` — търсене на `lib/` директория +## 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 -``` +**Проблем (преди):** `getLibPath()` винаги връщаше `appDir/lib`, което правеше невъзможно проекти от други директории да използват свои `lib/` пътища. -Когато `cljnim` е инсталиран в `/home/ziko/lispnim/`, `candidate1` винаги е `/home/ziko/lispnim/lib` и връща това. Проекти от други директории не могат да добавят свои `lib/` пътища. +**Решение:** +- Добавен `--lib-path ` глобален CLI флаг. +- `getLibPath()` сега проверява в следния ред: + 1. CLI `--lib-path` override + 2. `CLJNIM_LIB_PATH` environment variable + 3. Текуща директория `lib/` + 4. Приложение `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 +# Със собствена 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` +## 7. Поддръжка на `:paths` в `deps.edn` ✅ Имплементирано -**Проблем:** `deps.nim` парсира само `:deps` от `deps.edn`, но не и `:paths`. +**Проблем (преди):** `deps.nim` парсираше само `:deps` от `deps.edn`, но не и `:paths`. -**Причина:** -```nim -proc parseDepsMap(depsMap: CljVal): seq[Dependency] = - # Парсира само :deps +**Решение:** Добавен е парсинг на `:paths` от `deps.edn` и се включват в `searchPaths` при компилация. + +**Пример (сега работи):** +```clojure +;; deps.edn +{:paths ["src" "lib"] + :deps {org.clojure/core.async {:mvn/version "1.6.681"}}} ``` -**Препоръка:** Да се добави парсинг на `:paths` и да се включват в `searchPaths` при компилация. - -**Workaround за сега:** Clojure файловете трябва да са в структура, която съвпада с namespace имената, относително към текущата директория. - --- -## 8. `loop` с `if/else` — проблеми с `discard` +## 8. `loop` с `if/else` — проблеми с `discard` ✅ Имплементирано -**Проблем:** +**Проблем (преди):** ```clojure (loop [pairs (seq headers)] (if (empty? pairs) @@ -175,71 +174,114 @@ proc parseDepsMap(depsMap: CljVal): seq[Dependency] = 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:` блок не винаги връща стойност във всички клонове. +**Решение:** +- Добавен е `loopResult` променлива, която събира резултата. +- Подобрено е генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност правилно. +- Добавена е `needsValue` логика — `loop` се wrap-ва в IIFE `(proc(): CljVal = ...)()` когато е в expression context (напр. като аргумент на функция, в `let` RHS, или като последна стойност в програма). -**Препоръка:** Да се подобри генерирането на `loop` така, че всички клонове на `if` вътре да връщат стойност или да се използва `result = ...` pattern. +**Пример (сега работи):** +```clojure +(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))))))) -**Workaround за сега:** Използвайте `filter` + `first` вместо ръчен `loop`/`recur`. +(find-header [[:content-type "text/html"] [:accept "*/*"]] :accept) +;; => "*/*" +``` --- -## 9. Конфликти на имена между Clojure и Nim +## 9. Конфликти на имена между Clojure и Nim ✅ Имплементирано **Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи: - `parseQueryString` ≡ `parse_query_string` ≡ `parsequerystring` -Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име. Това води до: -- Презаписване на функцията -- Безкрайна рекурсия, ако едната извиква другата +Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име. -**Препоръка:** Да се добави prefix към всички генерирани Nim процедури, например `clj_`: +**Решение:** Добавен е `clj_` prefix към всички генерирани Nim процедури от Clojure код: ```nim proc clj_parse_query_string(...) ; вместо parse_query_string ``` - -**Workaround за сега:** В Nim runtime модулите използвайте уникални имена с префикс (напр. `bringParseQueryString`). +За `nim/` interop се използва `sanitizeNimIdent()` без prefix, така че native Nim имената да не се чупят. --- -## 10. `when isMainModule` в генерирания код +## 10. `when isMainModule` в генерирания код ✅ Имплементирано -**Проблем:** `when isMainModule` в края на генерирания Nim файл означава, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни. Това прави невъзможно създаването на "библиотечни" Clojure файлове, които съдържат и дефиниции, и изпълним код. +**Проблем (преди):** `when isMainModule` в края на генерирания Nim файл означаваше, че ако файлът се импортира от друг Nim модул, `main` кодът няма да се изпълни. -**Препоръка:** Да се добави флаг `--lib` или подобен, който да генерира код без `when isMainModule` за библиотечни файлове. - -**Workaround за сега:** Използвайте `cljnim compile file.clj output.nim` и после ръчно `nim c output.nim`. +**Решение:** `emitProgramLib` сега skip-ва `when isMainModule` guard, което позволява генерирането на "библиотечни" Clojure файлове. --- -## 11. Поддръжка на `try`/`catch`/`finally` +## 11. Поддръжка на `try`/`catch`/`finally` ✅ Имплементирано -**Статус:** Изглежда се поддържа, но не беше тествана обстойно по време на разработката на BRing. +**Решение:** +- `finally` блок сега правилно discard-ва expression резултатите. +- `catch` handler използва оригиналното име за scope tracking. -**Препоръка:** Да се добавят тестове за exception handling в HTTP контекст (грешки при парсиране, мрежови грешки). +**Пример (сега работи):** +```clojure +(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). + +**Пример (сега работи):** +```clojure +(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 +``` --- ## Обобщена таблица -| # | Проблем | Трудност | Приоритет | -|---|---------|----------|-----------| -| 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` | Лесно | Нисък | +| # | Проблем | Статус | 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](https://github.com/) — нативна Clojure уеб библиотека за cljnim.* +*Последно обновяване: 12 май 2026* diff --git a/README.md b/README.md index 01152c9..059498c 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Hello, native world! | 🇬🇧 English | [docs/en/index.md](docs/en/index.md) | Getting started, architecture, AI integration, API reference | | 🇧🇬 Български | [docs/bg/index.md](docs/bg/index.md) | Първи стъпки, архитектура, AI интеграция, API справочник | | 🧪 Test Suite | [docs/en/07-clojure-test-suite.md](docs/en/07-clojure-test-suite.md) | Cross-dialect compliance with [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) | +| 🛠️ Recommendations | [CLJNIM_RECOMMENDATIONS.md](CLJNIM_RECOMMENDATIONS.md) | Compiler limitations, workarounds, and fixes (Bulgarian) | ## ✨ What Makes This Unique? @@ -127,6 +128,11 @@ No JNI, no JVM bridging, no Java interop overhead. | AI integration (DeepSeek, OpenAI, MiMo) | ✅ | | Cross-compilation targets (JS, WASM, shared libs) | ✅ | | Self-hosted REPL with tree-walking interpreter | ✅ | +| First-class functions (defn as values) | ✅ | +| Multi-arity `defn` | ✅ | +| `&` rest parameters | ✅ | +| Keyword-as-function `(:key map)` | ✅ | +| `--lib-path` CLI flag + `CLJNIM_LIB_PATH` | ✅ | | 276+ unit tests | ✅ | | [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) compliance | ✅ | @@ -192,6 +198,12 @@ make build ./cljnim --help ``` +### Custom Library Path +Use `--lib-path` to point to your project's Nim runtime libraries: +```bash +./cljnim --lib-path ./my_project/lib run app.clj +``` + ## 🏗️ Architecture ``` diff --git a/TASKS.md b/TASKS.md index e31b0e5..4badc7d 100644 --- a/TASKS.md +++ b/TASKS.md @@ -113,6 +113,26 @@ These tasks are **small, well-defined, and high impact**: | T10.6 | Reader edge cases | ✅ | 🟢 | `src/reader.nim`, `tests/test_reader.nim` | `-.5`, `+.25`, `1e5`, `1.5e-3`, `-.5e-3`, `.25`. Scientific notation. 13 tests. | | T10.7 | CljVal type unification | ✅ | 🔴 | `src/types.nim`, `lib/cljnim_runtime.nim`, `src/runtime.nim`, `lib/cljnim_runtime_js.nim` | Identical CljKind enum (15 values) across all 4 files. All case statements cover new variants. | +## Phase 11: BRing Compiler Fixes (12 May 2026) + +Real-world fixes discovered during BRing web framework development. + +| ID | Task | Status | Complexity | Files | Acceptance Criteria | +|---|---|---|---|---|---| +| T11.1 | Docstrings in `defn`/`defn-` | ✅ | 🟢 | `src/emitter.nim` | `(defn f "doc" [x] ...)` compiles correctly. | +| T11.2 | Multi-arity `defn` | ✅ | 🟡 | `src/emitter.nim` | `(defn f ([x] ...) ([x y] ...))` dispatches by `args.len`. | +| T11.3 | Keyword-as-function `(:key map)` | ✅ | 🟢 | `src/emitter.nim` | `(:name person)` emits `cljGet(person, keyword)` instead of `cljApply`. | +| T11.4 | `&` rest parameters | ✅ | 🟡 | `src/emitter.nim` | `(defn f [x & rest] ...)` accepts variadic args as `seq[CljVal]`. | +| T11.5 | Nim interop name mangling | ✅ | 🟢 | `src/emitter.nim` | `nim/foo-bar` emits `foo_bar` in Nim. `sanitizeNimIdent` for interop chains. | +| T11.6 | `getLibPath()` + `--lib-path` flag | ✅ | 🟢 | `src/cljnim.nim`, `src/repl.nim` | `--lib-path ` CLI flag. Checks CLI → env → cwd → appDir. | +| T11.7 | `:paths` in `deps.edn` | ✅ | 🟢 | `src/deps.nim` | Parses `:paths` and includes in `searchPaths`. | +| T11.8 | `loop` + `if/else` discard fix | ✅ | 🟡 | `src/emitter.nim` | `loopResult` variable. `loop` returns value correctly in all branches. | +| T11.9 | `clj_` prefix for identifiers | ✅ | 🟢 | `src/emitter.nim` | All generated procs get `clj_` prefix. Nim interop excluded. | +| T11.10 | `when isMainModule` skip for libs | ✅ | 🟢 | `src/emitter.nim` | `emitProgramLib` skips `when isMainModule` guard. | +| T11.11 | `try`/`catch`/`finally` fixes | ✅ | 🟢 | `src/emitter.nim` | `finally` discards expression results. `catch` uses original name for scope. | +| T11.12 | First-class `defn` functions | ✅ | 🟡 | `src/emitter.nim` | `defn` symbols used as values emit `cljFn(...)` wrapper. Direct calls unchanged. | +| T11.13 | `loop` in expression context (IIFE) | ✅ | 🟡 | `src/emitter.nim` | `loop` wraps in `(proc(): CljVal = ...)()` when `needsValue=true`. | + --- ## How to claim a task diff --git a/docs/bg/05-user-guide.md b/docs/bg/05-user-guide.md index 71a8900..04ded65 100644 --- a/docs/bg/05-user-guide.md +++ b/docs/bg/05-user-guide.md @@ -33,6 +33,21 @@ make build ``` Компилира до Nim, после до C, после до бинарен файл, и го изпълнява. +### Глобални Флагове + +#### `--lib-path ` — Потребителска Директория за Библиотеки +Презаписва стандартния път за търсене на `lib/`. Проверява се преди `CLJNIM_LIB_PATH` environment променлива и вградените пътища. + +```bash +./cljnim --lib-path ./my_project/lib run app.clj +``` + +Алтернатива чрез environment променлива: +```bash +export CLJNIM_LIB_PATH=./my_project/lib +./cljnim run app.clj +``` + ### `read` — Парсиране и Отпечатване на AST ```bash ./cljnim read examples/hello.clj @@ -61,8 +76,24 @@ make build (defn greet [name] (println "Здравей, " name)) +; Функция с docstring +(defn greet "Поздравява някого" [name] + (str "Здравей, " name)) + +; Multi-arity функция +(defn greet-multi + ([name] (greet-multi name "Здравей")) + ([name greeting] (str greeting ", " name))) + +; Функция с rest параметри +(defn sum-all [x & rest] + (+ x (reduce + rest))) + ; Извикване на функция (greet "Свят") +(greet-multi "Алиса") ;; => "Здравей, Алиса" +(greet-multi "Алиса" "Здрасти") ;; => "Здрасти, Алиса" +(sum-all 1 2 3 4) ;; => 10 ; Аритметика (+ 1 2 3) ; => 6 @@ -88,6 +119,10 @@ make build ; Ключови думи (def person {:name "Алиса" :age 30}) +; Ключова дума като функция (търсене в карта) +(:name person) ;; => "Алиса" +(:age person) ;; => 30 + ; Картите и множествата използват persistent HAMT структури ; със structural sharing и O(log₃₂ n) операции. ``` @@ -102,6 +137,33 @@ make build (println (factorial 5)) ; => 120 ``` +### Функции от Първи Клас +Потребителските функции могат да се подават като стойности на функции от по-висок ред: + +```clojure +(defn square [x] (* x x)) +(defn odd? [x] (= 1 (mod x 2))) + +(map square [1 2 3 4]) ;; => [1 4 9 16] +(filter odd? [1 2 3 4]) ;; => [1 3] +(apply + [1 2 3]) ;; => 6 +``` + +### Loop / Recur +```clojure +(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) +;; => "*/*" +``` + ## Ръководство за AI REPL JSON REPL е проектиран за програмно взаимодействие. diff --git a/docs/en/05-user-guide.md b/docs/en/05-user-guide.md index b6ac8a9..83e86bc 100644 --- a/docs/en/05-user-guide.md +++ b/docs/en/05-user-guide.md @@ -33,6 +33,21 @@ Generates a `.nim` file from Clojure source. ``` Compiles to Nim, then to C, then to a binary, and runs it. +### Global Flags + +#### `--lib-path ` — Custom Library Directory +Override the default `lib/` search path. Checked before `CLJNIM_LIB_PATH` env var and built-in paths. + +```bash +./cljnim --lib-path ./my_project/lib run app.clj +``` + +Environment variable alternative: +```bash +export CLJNIM_LIB_PATH=./my_project/lib +./cljnim run app.clj +``` + ### `read` — Parse and Print AST ```bash ./cljnim read examples/hello.clj @@ -61,8 +76,24 @@ Shows the Clojure AST as S-expressions. (defn greet [name] (println "Hello, " name)) +; Function with docstring +(defn greet "Says hello to someone" [name] + (str "Hello, " name)) + +; Multi-arity function +(defn greet-multi + ([name] (greet-multi name "Hello")) + ([name greeting] (str greeting ", " name))) + +; Function with rest parameters +(defn sum-all [x & rest] + (+ x (reduce + rest))) + ; Call a function (greet "World") +(greet-multi "Alice") ;; => "Hello, Alice" +(greet-multi "Alice" "Hi") ;; => "Hi, Alice" +(sum-all 1 2 3 4) ;; => 10 ; Arithmetic (+ 1 2 3) ; => 6 @@ -88,6 +119,10 @@ Shows the Clojure AST as S-expressions. ; Keywords (def person {:name "Alice" :age 30}) +; Keyword as function (lookup in map) +(:name person) ;; => "Alice" +(:age person) ;; => 30 + ; Maps and sets use persistent HAMT data structures ; with structural sharing and O(log₃₂ n) operations. ``` @@ -102,6 +137,33 @@ Shows the Clojure AST as S-expressions. (println (factorial 5)) ; => 120 ``` +### First-Class Functions +User-defined functions can be passed as values to higher-order functions: + +```clojure +(defn square [x] (* x x)) +(defn odd? [x] (= 1 (mod x 2))) + +(map square [1 2 3 4]) ;; => [1 4 9 16] +(filter odd? [1 2 3 4]) ;; => [1 3] +(apply + [1 2 3]) ;; => 6 +``` + +### Loop / Recur +```clojure +(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) +;; => "*/*" +``` + ## AI REPL Guide The JSON REPL is designed for programmatic interaction.