Compare commits

..

10 Commits

Author SHA1 Message Date
dimgigov ab29c74299 fix(ci): install make in nim docker image before running tests 2026-05-12 20:21:34 +03:00
dimgigov b207cc1c77 fix: remove broken bring_http symlink from git 2026-05-12 20:09:52 +03:00
dimgigov 4b208dbe55 fix: threading macros, REPL stability, and CI coverage
- fix(emitter): emitFnWrapper now uses runtimeName for builtin operators
- fix(emitter): map/reduce handlers support variadic runtime functions (+, -, etc.)
- fix(emitter): variadic runtime functions list extracted to variadicRuntimeFns constant
- fix(macros): restore correct thread-first behavior (->) and fix result shadowing
- fix(macros): and/or/as->/some->/some->> macro result variable fixes
- fix(repl): add macroexpand step before interpreter eval
- fix(runtime): cljReduceSeq handles nil init (uses first coll element)
- fix(eval): add macroexpand/macroexpand-1 special forms to interpreter
- fix(eval): add macros module import for macroexpand access
- fix(warnings): eliminate ResultShadowed and UnusedImport warnings
- chore(.gitignore): remove artifact entries (-o, =0.4.0), unignore lib/bring_http.nim
- chore(ci): run all examples + threading macro + macroexpand REPL smoke tests
2026-05-12 20:02:58 +03:00
dimgigov 014a192c5d fix(runtime): cljAssocIn handles vectors as keys path
fix(emitter): remove contains? from special-forms list (handled elsewhere)
2026-05-12 18:55:27 +03:00
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
dimgigov a1a65f1c27 fix(emitter): loop in expression context via needsValue + IIFE wrapper
- Add needsValue parameter to emitExpr/emitSpecialForm to track expression context
- loop wraps in (proc(): CljVal = ... )() when needsValue=true
- Function call args, let/def values, vector/map literals pass needsValue=true
- emitProgramInternal passes needsValue=isLast for top-level forms
- Fixes: loop as function arg, loop in let RHS, loopResult at top level
2026-05-12 12:54:00 +03:00
dimgigov aac39a08ca feat(emitter): first-class defn functions via cljFn wrapper
User-defined defn functions can now be passed as values to other
functions (e.g. bring/run-server, apply-fn, map, filter).

- Track function arities in definedFnArities table
- Register defn/defn- arities during emission
- Emit cljFn wrapper when a defn symbol is used as a value:
  * Multi-arity / rest: cljFn(procName)  (already seq[CljVal])
  * Single-arity: cljFn(proc(args): procName(args[0], ...))
- Direct calls remain unchanged (fast path)
- All existing emitter tests pass
2026-05-12 11:42:07 +03:00
dimgigov 9a8519df87 feat(cli): add --lib-path flag and unify getLibPath logic
- Add --lib-path <dir> global CLI flag (parsed before subcommand)
- getLibPath() now checks CLI override first, then CLJNIM_LIB_PATH env,
  then current dir lib/, then app dir lib/
- nimCompile always includes compiler runtime lib path so cljnim_runtime
  is found even when --lib-path points to a project-specific directory
- Sync repl.nim getLibPath() to match cljnim.nim (env var + current dir)
2026-05-12 11:28:12 +03:00
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
dimgigov 3aaf3c0548 fix(emitter): avoid clj_ prefix on nim/ interop identifiers
The clj_ prefix added in fb8928e broke nim/module/proc interop because
native Nim identifiers were being mangled to clj_foo instead of foo.

- Extract sanitizeNimIdent from mangleName (does all cleanups except prefix)
- Use sanitizeNimIdent for nim/ interop function chains
- Regular Clojure symbols still get clj_ prefix via mangleName

Fixes BRing bringRunServer and other native Nim proc calls.
2026-05-12 11:16:16 +03:00
13 changed files with 700 additions and 128 deletions
+1 -2
View File
@@ -17,8 +17,6 @@ test_ai_assist
# Artifacts / temp files # Artifacts / temp files
-o
=0.4.0
__pycache__/ __pycache__/
test_single_debug.py test_single_debug.py
@@ -26,3 +24,4 @@ test_single_debug.py
src/tui_config src/tui_config
experiments/native-lib/test_client/test_client experiments/native-lib/test_client/test_client
experiments/wasm/tools/zig/ experiments/wasm/tools/zig/
lib/bring_http.nim
+11
View File
@@ -4,6 +4,9 @@ stages:
- test - test
- build - build
before_script:
- apt-get update -qq && apt-get install -y -qq make
test: test:
stage: test stage: test
script: script:
@@ -14,8 +17,12 @@ test:
- echo "=== math ===" && ./cljnim run examples/math.clj - echo "=== math ===" && ./cljnim run examples/math.clj
- echo "=== macros ===" && ./cljnim run examples/macros.clj - echo "=== macros ===" && ./cljnim run examples/macros.clj
- echo "=== ai_tools ===" && ./cljnim run examples/ai_tools.clj || true - echo "=== ai_tools ===" && ./cljnim run examples/ai_tools.clj || true
- echo "=== ai_features ===" && ./cljnim run examples/ai_features.clj || true
- echo "=== ffi ===" && ./cljnim run examples/ffi.clj - echo "=== ffi ===" && ./cljnim run examples/ffi.clj
- echo "=== interop ===" && ./cljnim run examples/interop.clj - echo "=== interop ===" && ./cljnim run examples/interop.clj
- echo "=== fizzbuzz ===" && ./cljnim run examples/fizzbuzz.clj
- echo "=== jsonista ===" && ./cljnim run examples/jsonista.clj
- echo "=== wordfreq ===" && ./cljnim run examples/wordfreq.clj
# Smoke test REPL (interpreter path) # Smoke test REPL (interpreter path)
- printf '{"op":"eval","form":"(+ 1 2 3)"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3 - printf '{"op":"eval","form":"(+ 1 2 3)"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3
# Smoke test REPL (tool-call format) # Smoke test REPL (tool-call format)
@@ -27,6 +34,10 @@ test:
- printf '{"op":"eval","form":"(let [c (chan 3)] (>! c 42) (<! c))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3 - printf '{"op":"eval","form":"(let [c (chan 3)] (>! c 42) (<! c))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3
# Test agents in REPL # Test agents in REPL
- printf '{"op":"eval","form":"(do (def a (agent 0)) (send a inc) (deref a))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -4 - printf '{"op":"eval","form":"(do (def a (agent 0)) (send a inc) (deref a))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -4
# Test threading macros in REPL
- printf '{"op":"eval","form":"(-> [1 2 3] (map inc))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3
- printf '{"op":"eval","form":"(->> [1 2 3] (map inc) (reduce +))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3
- printf '{"op":"eval","form":"(macroexpand (quote (-> [1 2 3] (map inc))))"}\n{"op":"quit"}\n' | timeout 5 ./cljnim repl --json | head -3
# Test compiled path # Test compiled path
- ./cljnim -e '(+ 10 20)' - ./cljnim -e '(+ 10 20)'
- ./cljnim -e '(quot 17 3)' - ./cljnim -e '(quot 17 3)'
+287
View File
@@ -0,0 +1,287 @@
# Препоръки към cljnim компилатора
> Събрани по време на разработката на BRing — Ring-подобна уеб библиотека за Bara Lang.
>
> **Последно обновяване:** 12 май 2026
---
## 1. Поддръжка на docstrings в `defn` ✅ Имплементирано
**Проблем (преди):**
```clojure
(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]` като параметри.
**Пример (сега работи):**
```clojure
(defn greet "Says hello" [name]
(str "Hello " name))
```
---
## 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 синтаксисът сега се разпознава. Генерира се dispatch по `args.len` в Nim.
**Пример (сега работи):**
```clojure
(defn greet
([name] (greet name "Hello"))
([name greeting] (str greeting " " name)))
(greet "Alice") ;; => "Hello Alice"
(greet "Alice" "Hi") ;; => "Hi Alice"
```
---
## 3. `(:key map)` синтаксис (keyword като функция) ✅ Имплементирано
**Проблем (преди):**
```clojure
(:request-method req) ; Clojure стандарт
```
Генерираше `cljApply(...)` вместо `cljGet(...)` и гърмеше с:
```
apply requires a function
```
**Решение:** В `emitSpecialForm`, когато `items[0].kind == ckKeyword`, сега се генерира `cljGet(args[0], keyword)` вместо `cljApply`.
**Пример (сега работи):**
```clojure
(def req {:request-method :get :uri "/hello"})
(:request-method req) ;; => :get
(:uri req) ;; => "/hello"
```
---
## 4. `&` rest параметри в `defn` ✅ Имплементирано
**Проблем (преди):**
```clojure
(defn greet [name & [greeting]]
...)
```
Гърмеше с:
```
Error: unhandled exception: defn params must be symbols [EmitterError]
```
**Решение:** `&` сега се разпознава като специален символ в параметрите. Генерира се Nim код, който приема `seq[CljVal]` и разпределя аргументите.
**Пример (сега работи):**
```clojure
(defn sum-all [x & rest]
(+ x (reduce + rest)))
(sum-all 1 2 3 4) ;; => 10
```
---
## 5. Nim interop — mangle на имената ✅ Имплементирано
**Проблем (преди):**
```clojure
(nim/bring_http/run-server handler port)
```
Генерираше:
```nim
run-server(handler, port)
```
А `-` не е валиден символ в Nim идентификатори.
**Решение:** В `emitSpecialForm` за `nim/` interop се прилага `sanitizeNimIdent()`, която заменя `-` с `_` и премахва други невалидни символи за Nim.
**Пример (сега работи):**
```clojure
(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/`
**Пример:**
```bash
# Със собствена 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` при компилация.
**Пример (сега работи):**
```clojure
;; deps.edn
{:paths ["src" "lib"]
:deps {org.clojure/core.async {:mvn/version "1.6.681"}}}
```
---
## 8. `loop` с `if/else` — проблеми с `discard` ✅ Имплементирано
**Проблем (преди):**
```clojure
(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, или като последна стойност в програма).
**Пример (сега работи):**
```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)
;; => "*/*"
```
---
## 9. Конфликти на имена между Clojure и Nim ✅ Имплементирано
**Проблем:** Nim е case-insensitive за първата буква и игнорира `_`. Значи:
- `parseQueryString``parse_query_string``parsequerystring`
Ако cljnim генерира `proc parse_query_string` и съществува `proc parseQueryString` в импортиран модул, Nim ги третира като едно и също име.
**Решение:** Добавен е `clj_` prefix към всички генерирани Nim процедури от Clojure код:
```nim
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.
**Пример (сега работи):**
```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
```
---
## Обобщена таблица
| # | Проблем | Статус | 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*
+12
View File
@@ -58,6 +58,7 @@ Hello, native world!
| 🇬🇧 English | [docs/en/index.md](docs/en/index.md) | Getting started, architecture, AI integration, API reference | | 🇬🇧 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 справочник | | 🇧🇬 Български | [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) | | 🧪 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? ## ✨ What Makes This Unique?
@@ -127,6 +128,11 @@ No JNI, no JVM bridging, no Java interop overhead.
| AI integration (DeepSeek, OpenAI, MiMo) | ✅ | | AI integration (DeepSeek, OpenAI, MiMo) | ✅ |
| Cross-compilation targets (JS, WASM, shared libs) | ✅ | | Cross-compilation targets (JS, WASM, shared libs) | ✅ |
| Self-hosted REPL with tree-walking interpreter | ✅ | | 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 | ✅ | | 276+ unit tests | ✅ |
| [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) compliance | ✅ | | [jank-lang/clojure-test-suite](https://github.com/jank-lang/clojure-test-suite) compliance | ✅ |
@@ -192,6 +198,12 @@ make build
./cljnim --help ./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 ## 🏗️ Architecture
``` ```
+20
View File
@@ -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.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. | | 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 <dir>` 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 ## How to claim a task
+62
View File
@@ -33,6 +33,21 @@ make build
``` ```
Компилира до Nim, после до C, после до бинарен файл, и го изпълнява. Компилира до Nim, после до C, после до бинарен файл, и го изпълнява.
### Глобални Флагове
#### `--lib-path <dir>` — Потребителска Директория за Библиотеки
Презаписва стандартния път за търсене на `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 ### `read` — Парсиране и Отпечатване на AST
```bash ```bash
./cljnim read examples/hello.clj ./cljnim read examples/hello.clj
@@ -61,8 +76,24 @@ make build
(defn greet [name] (defn greet [name]
(println "Здравей, " 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 "Свят")
(greet-multi "Алиса") ;; => "Здравей, Алиса"
(greet-multi "Алиса" "Здрасти") ;; => "Здрасти, Алиса"
(sum-all 1 2 3 4) ;; => 10
; Аритметика ; Аритметика
(+ 1 2 3) ; => 6 (+ 1 2 3) ; => 6
@@ -88,6 +119,10 @@ make build
; Ключови думи ; Ключови думи
(def person {:name "Алиса" :age 30}) (def person {:name "Алиса" :age 30})
; Ключова дума като функция (търсене в карта)
(:name person) ;; => "Алиса"
(:age person) ;; => 30
; Картите и множествата използват persistent HAMT структури ; Картите и множествата използват persistent HAMT структури
; със structural sharing и O(log₃₂ n) операции. ; със structural sharing и O(log₃₂ n) операции.
``` ```
@@ -102,6 +137,33 @@ make build
(println (factorial 5)) ; => 120 (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 ## Ръководство за AI REPL
JSON REPL е проектиран за програмно взаимодействие. JSON REPL е проектиран за програмно взаимодействие.
+62
View File
@@ -33,6 +33,21 @@ Generates a `.nim` file from Clojure source.
``` ```
Compiles to Nim, then to C, then to a binary, and runs it. Compiles to Nim, then to C, then to a binary, and runs it.
### Global Flags
#### `--lib-path <dir>` — 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 ### `read` — Parse and Print AST
```bash ```bash
./cljnim read examples/hello.clj ./cljnim read examples/hello.clj
@@ -61,8 +76,24 @@ Shows the Clojure AST as S-expressions.
(defn greet [name] (defn greet [name]
(println "Hello, " 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 ; Call a function
(greet "World") (greet "World")
(greet-multi "Alice") ;; => "Hello, Alice"
(greet-multi "Alice" "Hi") ;; => "Hi, Alice"
(sum-all 1 2 3 4) ;; => 10
; Arithmetic ; Arithmetic
(+ 1 2 3) ; => 6 (+ 1 2 3) ; => 6
@@ -88,6 +119,10 @@ Shows the Clojure AST as S-expressions.
; Keywords ; Keywords
(def person {:name "Alice" :age 30}) (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 ; Maps and sets use persistent HAMT data structures
; with structural sharing and O(log₃₂ n) operations. ; with structural sharing and O(log₃₂ n) operations.
``` ```
@@ -102,6 +137,33 @@ Shows the Clojure AST as S-expressions.
(println (factorial 5)) ; => 120 (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 ## AI REPL Guide
The JSON REPL is designed for programmatic interaction. The JSON REPL is designed for programmatic interaction.
+20 -9
View File
@@ -1476,9 +1476,17 @@ proc cljFilterSeq*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): seq[C
result.add(item) result.add(item)
proc cljReduceSeq*(f: proc(args: seq[CljVal]): CljVal, init: CljVal, coll: seq[CljVal]): CljVal = proc cljReduceSeq*(f: proc(args: seq[CljVal]): CljVal, init: CljVal, coll: seq[CljVal]): CljVal =
result = init if coll.len == 0:
for item in coll: return init
result = f(@[result, item]) var i = 0
if init.isNil or init.kind == ckNil:
result = coll[0]
i = 1
else:
result = init
while i < coll.len:
result = f(@[result, coll[i]])
i += 1
proc cljMap*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal = proc cljMap*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal =
if coll.isNil: return cljList(@[]) if coll.isNil: return cljList(@[])
@@ -2080,12 +2088,15 @@ proc cljUpdate*(m: CljVal, key: CljVal, f: CljVal, extra: seq[CljVal] = @[]): Cl
else: raise newException(CatchableError, "update requires a function") else: raise newException(CatchableError, "update requires a function")
proc cljAssocIn*(m: CljVal, keys: CljVal, val: CljVal): CljVal = proc cljAssocIn*(m: CljVal, keys: CljVal, val: CljVal): CljVal =
if keys.isNil or keys.kind != ckList or keys.listItems.len == 0: if keys.isNil: return m
return m let isList = keys.kind == ckList or keys.kind == ckVector
if keys.listItems.len == 1: if not isList: return m
return cljAssoc(m, keys.listItems[0], val) let keyItems = if keys.kind == ckList: keys.listItems else: keys.vecData.toSeq
let firstKey = keys.listItems[0] if keyItems.len == 0: return m
let restKeys = cljList(keys.listItems[1..^1]) if keyItems.len == 1:
return cljAssoc(m, keyItems[0], val)
let firstKey = keyItems[0]
let restKeys = cljList(keyItems[1..^1])
let inner = cljGet(m, firstKey) let inner = cljGet(m, firstKey)
let updated = cljAssocIn(inner, restKeys, val) let updated = cljAssocIn(inner, restKeys, val)
cljAssoc(m, firstKey, updated) cljAssoc(m, firstKey, updated)
+28 -10
View File
@@ -2,8 +2,13 @@
import os, osproc, strutils, times import os, osproc, strutils, times
import reader, emitter, types, repl, macros, deps, ai_assist, tui import reader, emitter, types, repl, macros, deps, ai_assist, tui
var libPathOverride* = ""
proc getLibPath(): string = proc getLibPath(): string =
# Check environment variable first # CLI --lib-path takes highest precedence
if libPathOverride.len > 0 and dirExists(libPathOverride):
return libPathOverride
# Check environment variable
let envPath = getEnv("CLJNIM_LIB_PATH", "") let envPath = getEnv("CLJNIM_LIB_PATH", "")
if envPath.len > 0 and dirExists(envPath): if envPath.len > 0 and dirExists(envPath):
return envPath return envPath
@@ -131,9 +136,13 @@ proc compileFileLib*(inputPath: string, outputPath: string, extraPaths: seq[stri
proc nimCompile(nimPath: string, binPath: string, release: bool = false): tuple[exitCode: int, output: string] = proc nimCompile(nimPath: string, binPath: string, release: bool = false): tuple[exitCode: int, output: string] =
let libPath = getLibPath() let libPath = getLibPath()
let compilerLib = getAppDir() / "lib"
var cmd = "nim c" var cmd = "nim c"
if release: if release:
cmd.add(" -d:release") cmd.add(" -d:release")
# Always include compiler runtime lib so cljnim_runtime is found
if dirExists(compilerLib) and compilerLib != libPath:
cmd.add(" --path:" & quoteShell(compilerLib))
cmd.add(" --path:" & quoteShell(libPath)) cmd.add(" --path:" & quoteShell(libPath))
cmd.add(" -o:" & quoteShell(binPath) & " " & quoteShell(nimPath)) cmd.add(" -o:" & quoteShell(binPath) & " " & quoteShell(nimPath))
echo "Compiling: ", cmd echo "Compiling: ", cmd
@@ -215,20 +224,29 @@ proc runFile*(inputPath: string) =
proc main() = proc main() =
initBuiltinMacros() initBuiltinMacros()
let args = commandLineParams() var args = commandLineParams()
# Strip leading global flags
if args.len >= 2 and args[0] == "--lib-path":
libPathOverride = args[1]
args = args[2..^1]
if args.len == 0: if args.len == 0:
echo "Bara Lang — AI-First Compiler" echo "Bara Lang — AI-First Compiler"
echo "" echo ""
echo "Usage:" echo "Usage:"
echo " cljnim compile <file.clj> [output.nim] Compile to Nim source" echo " cljnim [--lib-path <dir>] compile <file.clj> [output.nim]"
echo " cljnim run <file.clj> Compile and run binary" echo " cljnim [--lib-path <dir>] run <file.clj>"
echo " cljnim read <file.clj> Parse and print AST" echo " cljnim [--lib-path <dir>] read <file.clj>"
echo " cljnim repl [--json] Start REPL (human or AI mode)" echo " cljnim repl [--json]"
echo " cljnim deps Resolve dependencies from deps.edn" echo " cljnim deps"
echo " cljnim -e '<code>' Evaluate expression" echo " cljnim -e '<code>'"
echo " cljnim ai '<description>' Generate Bara Lang code with AI" echo " cljnim ai '<description>'"
echo " cljnim tui Start interactive TUI" echo " cljnim tui"
echo ""
echo "Global Options:"
echo " --lib-path <dir> Additional Nim library search path"
echo " (also via CLJNIM_LIB_PATH env var)"
echo "" echo ""
echo "REPL Commands:" echo "REPL Commands:"
echo " :quit, :q Exit REPL" echo " :quit, :q Exit REPL"
+142 -73
View File
@@ -1,5 +1,5 @@
# Bara Lang → Nim Emitter # Bara Lang → Nim Emitter
import strutils, sets, algorithm, sequtils import strutils, sets, sequtils, tables
import types import types
import macros import macros
@@ -10,13 +10,74 @@ var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
var scopeStack*: seq[HashSet[string]] = @[] var scopeStack*: seq[HashSet[string]] = @[]
var definedGlobals*: HashSet[string] = initHashSet[string]() var definedGlobals*: HashSet[string] = initHashSet[string]()
var multiArityFns*: HashSet[string] = initHashSet[string]() var multiArityFns*: HashSet[string] = initHashSet[string]()
var definedFnArities*: Table[string, int] = initTable[string, int]() # -1 = multi-arity/rest
var loopResultVar*: string = "" # Set by loop handler when result capture is needed var loopResultVar*: string = "" # Set by loop handler when result capture is needed
# Runtime functions that accept seq[CljVal] (variadic wrapper signature)
let variadicRuntimeFns* = ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str",
"atom", "concat", "min", "max", "merge", "interleave",
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root",
"array-map", "inf", "nan", "list",
"float", "int", "double", "long", "short", "byte",
"boolean", "num", "number",
"make-hierarchy", "derive", "underive", "ancestors",
"descendants", "parents", "isa?", "promise", "create-ns", "future",
"delay",
"aclone", "alength", "aget", "int-array", "identical?", "empty", "identity",
"conj",
"drop-last", "shuffle", "repeatedly", "fnil", "intern",
"println-str", "prn-str", "binding", "aset",
"volatile!", "deliver", "doall", "dorun",
"to-array", "into-array", "vector", "rand", "rand-int",
"rand-nth", "random-sample",
"assoc", "dissoc", "get", "get-in", "update", "assoc-in",
"contains?", "select-keys", "keys", "vals",
"disj", "peek", "pop",
"transduce", "ex-info",
"compare", "subvec",
"require", "eval", "resolve", "random-uuid",
"vreset!", "restart-agent", "with-out-str",
"System/getProperty",
"dosync", "alter"]
proc registerGlobal*(name: string) = proc registerGlobal*(name: string) =
definedGlobals.incl(name) definedGlobals.incl(name)
proc mangleName*(name: string): string
proc runtimeName(op: string): string
proc registerFn*(name: string, arity: int) =
definedFnArities[name] = arity
proc emitFnWrapper*(name: string): string =
## Emit a cljFn wrapper for a user-defined function when used as a value.
let rtName = runtimeName(name)
if rtName.len > 0:
# Built-in runtime function — emit variadic wrapper if applicable
if name in variadicRuntimeFns:
return "cljFn(" & rtName & ")"
else:
return "cljFn(proc(args: seq[CljVal]): CljVal = " & rtName & "(args[0]))"
let mangled = mangleName(name)
if name in multiArityFns:
# Multi-arity / rest params already take seq[CljVal]
return "cljFn(" & mangled & ")"
elif definedFnArities.hasKey(name):
let arity = definedFnArities[name]
if arity == 0:
return "cljFn(proc(args: seq[CljVal]): CljVal = " & mangled & "())"
else:
var argRefs: seq[string] = @[]
for i in 0..<arity:
argRefs.add("args[" & $i & "]")
return "cljFn(proc(args: seq[CljVal]): CljVal = " & mangled & "(" & argRefs.join(", ") & "))"
else:
return mangled
proc clearGlobals*() = proc clearGlobals*() =
definedGlobals = initHashSet[string]() definedGlobals = initHashSet[string]()
definedFnArities = initTable[string, int]()
proc setNsAliases*(aliases: seq[(string, string)]) = proc setNsAliases*(aliases: seq[(string, string)]) =
nsAliases = aliases nsAliases = aliases
@@ -35,7 +96,8 @@ proc resolveNsAlias*(name: string): string =
type type
EmitterError* = object of CatchableError EmitterError* = object of CatchableError
proc mangleName*(name: string): string = proc sanitizeNimIdent*(name: string): string =
## Sanitize a string to be a valid Nim identifier, without the clj_ prefix.
result = "" result = ""
for c in name: for c in name:
case c case c
@@ -79,8 +141,9 @@ proc mangleName*(name: string): string =
"interface", "lambda", "open", "quit", "result", "nil", "end"] "interface", "lambda", "open", "quit", "result", "nil", "end"]
if result in nimKeywords: if result in nimKeywords:
result = result & "1" result = result & "1"
# Prefix to avoid conflicts with Nim identifiers (case-insensitive + underscore ignoring)
result = "clj_" & result proc mangleName*(name: string): string =
result = "clj_" & sanitizeNimIdent(name)
proc pushScope() = proc pushScope() =
scopeStack.add(initHashSet[string]()) scopeStack.add(initHashSet[string]())
@@ -99,7 +162,7 @@ proc isLocalVar(name: string): bool =
return true return true
return false return false
proc emitExpr*(v: CljVal, indent: int = 0): string proc emitExpr*(v: CljVal, indent: int = 0, needsValue: bool = false): string
proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string
proc emitQuotedForm*(v: CljVal): string proc emitQuotedForm*(v: CljVal): string
proc emitFnAsProc*(items: seq[CljVal], indent: int): string proc emitFnAsProc*(items: seq[CljVal], indent: int): string
@@ -110,15 +173,15 @@ proc indentStr(indent: int): string =
proc indentCode(code: string, extra: int): string = proc indentCode(code: string, extra: int): string =
if extra <= 0: return code if extra <= 0: return code
let prefix = indentStr(extra) let prefix = indentStr(extra)
var result = "" var res = ""
var lines = code.split("\n") var lines = code.split("\n")
for i, line in lines: for i, line in lines:
if i > 0: result.add("\n") if i > 0: res.add("\n")
if line.len > 0: if line.len > 0:
result.add(prefix & line) res.add(prefix & line)
else: else:
result.add(line) res.add(line)
return result return res
proc runtimeName(op: string): string = proc runtimeName(op: string): string =
case op case op
@@ -413,26 +476,26 @@ proc letDestructuredBindings(bindings: CljVal): CljVal =
# Expand let bindings with vector/map destructuring into simple symbol bindings # Expand let bindings with vector/map destructuring into simple symbol bindings
if bindings.kind != ckVector: if bindings.kind != ckVector:
return bindings return bindings
var result: seq[CljVal] = @[] var res: seq[CljVal] = @[]
var i = 0 var i = 0
while i < bindings.items.len: while i < bindings.items.len:
let bname = bindings.items[i] let bname = bindings.items[i]
let bval = bindings.items[i+1] let bval = bindings.items[i+1]
if bname.kind == ckSymbol: if bname.kind == ckSymbol:
result.add(bname) res.add(bname)
result.add(bval) res.add(bval)
elif bname.kind == ckVector: elif bname.kind == ckVector:
let tmp = cljSymbol("ds_" & $i) let tmp = cljSymbol("ds_" & $i)
result.add(tmp) res.add(tmp)
result.add(bval) res.add(bval)
for j in 0..<bname.items.len: for j in 0..<bname.items.len:
if bname.items[j].kind == ckSymbol: if bname.items[j].kind == ckSymbol:
result.add(bname.items[j]) res.add(bname.items[j])
result.add(cljList(@[cljSymbol("nth"), tmp, cljInt(j)])) res.add(cljList(@[cljSymbol("nth"), tmp, cljInt(j)]))
elif bname.kind == ckMap: elif bname.kind == ckMap:
let tmp = cljSymbol("ds_" & $i) let tmp = cljSymbol("ds_" & $i)
result.add(tmp) res.add(tmp)
result.add(bval) res.add(bval)
var hasAs = false var hasAs = false
var asName: CljVal = nil var asName: CljVal = nil
var j = 0 var j = 0
@@ -442,19 +505,19 @@ proc letDestructuredBindings(bindings: CljVal): CljVal =
if key.kind == ckKeyword and key.kwName == "keys" and val.kind == ckVector: if key.kind == ckKeyword and key.kwName == "keys" and val.kind == ckVector:
for k in 0..<val.items.len: for k in 0..<val.items.len:
if val.items[k].kind == ckSymbol: if val.items[k].kind == ckSymbol:
result.add(val.items[k]) res.add(val.items[k])
result.add(cljList(@[cljSymbol("get"), tmp, cljKeyword(val.items[k].symName)])) res.add(cljList(@[cljSymbol("get"), tmp, cljKeyword(val.items[k].symName)]))
elif key.kind == ckKeyword and key.kwName == "as": elif key.kind == ckKeyword and key.kwName == "as":
hasAs = true hasAs = true
asName = val asName = val
j += 1 j += 1
if hasAs and asName != nil and asName.kind == ckSymbol: if hasAs and asName != nil and asName.kind == ckSymbol:
result.add(asName) res.add(asName)
result.add(tmp) res.add(tmp)
i += 2 i += 2
return cljVector(result) return cljVector(res)
proc emitSpecialForm(items: seq[CljVal], indent: int): string = proc emitSpecialForm(items: seq[CljVal], indent: int, needsValue: bool = false): string =
let sp = indentStr(indent) let sp = indentStr(indent)
let head = items[0] let head = items[0]
if head.kind != ckSymbol: if head.kind != ckSymbol:
@@ -467,7 +530,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
# Head is not a symbol - evaluate as function call: ((fn ...) args...) # Head is not a symbol - evaluate as function call: ((fn ...) args...)
var argParts: seq[string] = @[] var argParts: seq[string] = @[]
for i in 1..<items.len: for i in 1..<items.len:
argParts.add(emitExpr(items[i], indent)) argParts.add(emitExpr(items[i], indent, needsValue = true))
let fnCode = emitExpr(head, 0) let fnCode = emitExpr(head, 0)
return sp & "cljApply(" & fnCode & ", cljList(@[" & argParts.join(", ") & "]))" return sp & "cljApply(" & fnCode & ", cljList(@[" & argParts.join(", ") & "]))"
let op = resolveNsAlias(head.symName) let op = resolveNsAlias(head.symName)
@@ -668,7 +731,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if nameItem.kind == ckSymbol: if nameItem.kind == ckSymbol:
let mangled = mangleName(nameItem.symName) let mangled = mangleName(nameItem.symName)
registerGlobal(nameItem.symName) registerGlobal(nameItem.symName)
let valCode = emitExpr(valItem, 0) let valCode = emitExpr(valItem, 0, needsValue = true)
return sp & "let " & mangled & " = " & valCode return sp & "let " & mangled & " = " & valCode
return sp & "cljNil()" return sp & "cljNil()"
var name = items[1] var name = items[1]
@@ -693,7 +756,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let valCode = emitFnAsProc(valForm.items, 0) let valCode = emitFnAsProc(valForm.items, 0)
emitLibMode = oldEmitLibMode emitLibMode = oldEmitLibMode
return sp & "let " & mangled & " = " & valCode return sp & "let " & mangled & " = " & valCode
let valCode = emitExpr(items[2], 0) let valCode = emitExpr(items[2], 0, needsValue = true)
return sp & "let " & mangled & " = " & valCode return sp & "let " & mangled & " = " & valCode
else: else:
# For nested def: use global var so it's accessible from closures # For nested def: use global var so it's accessible from closures
@@ -703,7 +766,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let valCode = emitFnAsProc(valForm.items, indent) let valCode = emitFnAsProc(valForm.items, indent)
emitLibMode = oldEmitLibMode emitLibMode = oldEmitLibMode
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
let valCode = emitExpr(items[2], indent) let valCode = emitExpr(items[2], indent, needsValue = true)
return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled
of "defn": of "defn":
@@ -805,6 +868,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
# Rest params: generate proc with args: seq[CljVal] # Rest params: generate proc with args: seq[CljVal]
# Register so call sites wrap args in @[...] # Register so call sites wrap args in @[...]
multiArityFns.incl(name.symName) multiArityFns.incl(name.symName)
registerFn(name.symName, -1)
let namedCount = restIdx let namedCount = restIdx
var preambleLines: seq[string] = @[] var preambleLines: seq[string] = @[]
for pi in 0..<restIdx: for pi in 0..<restIdx:
@@ -822,6 +886,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
popScope() popScope()
let preamble = preambleLines.join("\n") let preamble = preambleLines.join("\n")
return sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =\n" & preamble & "\n" & bodyCode return sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =\n" & preamble & "\n" & bodyCode
registerFn(name.symName, paramNames.len)
if indent == 0: if indent == 0:
var bodyCode = "" var bodyCode = ""
if body.len == 0: if body.len == 0:
@@ -893,6 +958,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if hasRest: if hasRest:
# Rest params: generate proc with args: seq[CljVal] # Rest params: generate proc with args: seq[CljVal]
multiArityFns.incl(name.symName) multiArityFns.incl(name.symName)
registerFn(name.symName, -1)
let namedCount = restIdx let namedCount = restIdx
var preambleLines: seq[string] = @[] var preambleLines: seq[string] = @[]
for pi in 0..<restIdx: for pi in 0..<restIdx:
@@ -910,6 +976,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
popScope() popScope()
let preamble = preambleLines.join("\n") let preamble = preambleLines.join("\n")
return sp & "proc " & procName & "(args: seq[CljVal]): CljVal {.used.} =\n" & preamble & "\n" & bodyCode return sp & "proc " & procName & "(args: seq[CljVal]): CljVal {.used.} =\n" & preamble & "\n" & bodyCode
registerFn(name.symName, paramNames.len)
var bodyCode = "" var bodyCode = ""
if body.len == 0: if body.len == 0:
bodyCode = indentStr(indent + 1) & "cljNil()" bodyCode = indentStr(indent + 1) & "cljNil()"
@@ -1007,7 +1074,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if bname.kind != ckSymbol: if bname.kind != ckSymbol:
raise newException(EmitterError, "let binding name must be a symbol, got " & $bname.kind & ": " & $bname) raise newException(EmitterError, "let binding name must be a symbol, got " & $bname.kind & ": " & $bname)
addToScope(bname.symName) addToScope(bname.symName)
let bcode = emitExpr(bval, indent + 1) let bcode = emitExpr(bval, indent + 1, needsValue = true)
if bcode.find("\n") != -1: if bcode.find("\n") != -1:
# Multi-line value: use var and place first line on next line for correct indentation # Multi-line value: use var and place first line on next line for correct indentation
lines.add(indentStr(indent + 1) & "var " & mangleName(bname.symName) & ": CljVal") lines.add(indentStr(indent + 1) & "var " & mangleName(bname.symName) & ": CljVal")
@@ -1177,7 +1244,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
lname = lname.items[1] lname = lname.items[1]
if lname.kind != ckSymbol: if lname.kind != ckSymbol:
raise newException(EmitterError, "loop binding name must be a symbol") raise newException(EmitterError, "loop binding name must be a symbol")
loopParams.add((mangleName(lname.symName), lname.symName, emitExpr(lval, 0))) loopParams.add((mangleName(lname.symName), lname.symName, emitExpr(lval, 0, needsValue = true)))
li += 2 li += 2
var loopVars: seq[string] = @[] var loopVars: seq[string] = @[]
var lines: seq[string] = @[] var lines: seq[string] = @[]
@@ -1242,7 +1309,17 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if needsResultVar: if needsResultVar:
lines.add(sp & "loopResult") lines.add(sp & "loopResult")
loopResultVar = "" loopResultVar = ""
return lines.join("\n") let loopCode = lines.join("\n")
if needsValue:
# Wrap in IIFE for expression contexts (function args, let RHS, etc.)
var iifeLines: seq[string] = @[]
iifeLines.add(sp & "(proc(): CljVal =")
iifeLines.add(indentCode(loopCode, 1))
if not needsResultVar:
iifeLines.add(indentStr(indent + 1) & "cljNil()")
iifeLines.add(sp & ")()")
return iifeLines.join("\n")
return loopCode
of "recur": of "recur":
if items.len < 2: if items.len < 2:
@@ -1460,7 +1537,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if fnArg.kind == ckSymbol: if fnArg.kind == ckSymbol:
let rn = runtimeName(fnArg.symName) let rn = runtimeName(fnArg.symName)
if rn.len > 0: if rn.len > 0:
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args[0])), " & collArg & ")" let isVar = fnArg.symName in variadicRuntimeFns
if isVar:
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & collArg & ")"
else:
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args[0])), " & collArg & ")"
return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & collArg & ")" return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & mangleName(fnArg.symName) & "(args[0])), " & collArg & ")"
elif fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]: elif fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"]:
let fnParams = fnArg.items[1] let fnParams = fnArg.items[1]
@@ -1533,6 +1614,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")" let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")"
return sp & "cljReduce(cljFn(" & fnProc & "), cljNil(), " & collArg & ")" return sp & "cljReduce(cljFn(" & fnProc & "), cljNil(), " & collArg & ")"
elif isSymbol: elif isSymbol:
let rn = runtimeName(fnArg.symName)
if rn.len > 0:
let wrapper = "proc(args: seq[CljVal]): CljVal = " & rn & "(args)"
return sp & "cljReduce(cljFn(" & wrapper & "), cljNil(), " & collArg & ")"
let mangled = mangleName(fnArg.symName) let mangled = mangleName(fnArg.symName)
let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])" let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])"
return sp & "cljReduce(cljFn(" & wrapper & "), cljNil(), " & collArg & ")" return sp & "cljReduce(cljFn(" & wrapper & "), cljNil(), " & collArg & ")"
@@ -1562,6 +1647,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")" let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; let " & pNames[1] & " = args[1]; " & bodyExpr & ")"
return sp & "cljReduce(cljFn(" & fnProc & "), " & initArg & ", " & collArg & ")" return sp & "cljReduce(cljFn(" & fnProc & "), " & initArg & ", " & collArg & ")"
elif isSymbol: elif isSymbol:
let rn = runtimeName(fnArg.symName)
if rn.len > 0:
let wrapper = "proc(args: seq[CljVal]): CljVal = " & rn & "(args)"
return sp & "cljReduce(cljFn(" & wrapper & "), " & initArg & ", " & collArg & ")"
let mangled = mangleName(fnArg.symName) let mangled = mangleName(fnArg.symName)
let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])" let wrapper = "proc(args: seq[CljVal]): CljVal = " & mangled & "(args[0], args[1])"
return sp & "cljReduce(cljFn(" & wrapper & "), " & initArg & ", " & collArg & ")" return sp & "cljReduce(cljFn(" & wrapper & "), " & initArg & ", " & collArg & ")"
@@ -1718,7 +1807,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
return sp & "cljNil()" return sp & "cljNil()"
var argParts: seq[string] = @[] var argParts: seq[string] = @[]
for i in 2..<items.len: for i in 2..<items.len:
argParts.add(emitExpr(items[i], indent)) argParts.add(emitExpr(items[i], indent, needsValue = true))
let mangled = mangleName(className.symName) let mangled = mangleName(className.symName)
return sp & mangled & "(" & argParts.join(", ") & ")" return sp & mangled & "(" & argParts.join(", ") & ")"
@@ -1958,10 +2047,11 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
let parts = op.replace(".", "/").split("/") let parts = op.replace(".", "/").split("/")
if parts.len >= 3: if parts.len >= 3:
let module = parts[1] let module = parts[1]
# Mangle each part of the function chain for Nim identifier validity # Sanitize each part of the function chain for Nim identifier validity
# (native Nim identifiers must NOT get the clj_ prefix)
var mangledParts: seq[string] = @[] var mangledParts: seq[string] = @[]
for p in parts[2..^1]: for p in parts[2..^1]:
mangledParts.add(mangleName(p)) mangledParts.add(sanitizeNimIdent(p))
var funcChain = mangledParts.join(".") var funcChain = mangledParts.join(".")
# Strip ? suffix (Clojure convention) — not valid in Nim # Strip ? suffix (Clojure convention) — not valid in Nim
if funcChain.endsWith("?"): if funcChain.endsWith("?"):
@@ -2045,7 +2135,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
if rn.len > 0: if rn.len > 0:
var argParts: seq[string] = @[] var argParts: seq[string] = @[]
for i in 1..<items.len: for i in 1..<items.len:
argParts.add(emitExpr(items[i], indent)) argParts.add(emitExpr(items[i], indent, needsValue = true))
# Variadic functions take seq[CljVal] # Variadic functions take seq[CljVal]
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
"println", "prn", "print", "str", "pr-str", "println", "prn", "print", "str", "pr-str",
@@ -2065,7 +2155,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
"to-array", "into-array", "vector", "rand", "rand-int", "to-array", "into-array", "vector", "rand", "rand-int",
"rand-nth", "random-sample", "rand-nth", "random-sample",
"assoc", "dissoc", "get", "get-in", "update", "assoc-in", "assoc", "dissoc", "get", "get-in", "update", "assoc-in",
"contains?", "select-keys", "select-keys",
"disj", "peek", "pop", "disj", "peek", "pop",
"transduce", "ex-info", "transduce", "ex-info",
"compare", "subvec", "compare", "subvec",
@@ -2131,7 +2221,7 @@ proc emitQuotedForm*(v: CljVal): string =
else: else:
return emitExpr(v, 0) return emitExpr(v, 0)
proc emitExpr*(v: CljVal, indent: int = 0): string = proc emitExpr*(v: CljVal, indent: int = 0, needsValue: bool = false): string =
let sp = indentStr(indent) let sp = indentStr(indent)
case v.kind case v.kind
of ckNil: of ckNil:
@@ -2160,32 +2250,7 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
let rn = runtimeName(symName) let rn = runtimeName(symName)
if rn.len > 0 and not isLocalVar(symName): if rn.len > 0 and not isLocalVar(symName):
# Check if the runtime function is variadic (takes seq[CljVal]) # Check if the runtime function is variadic (takes seq[CljVal])
let variadic = symName in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", let variadic = symName in variadicRuntimeFns
"println", "prn", "print", "str", "pr-str",
"atom", "concat", "min", "max", "merge", "interleave",
"zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root",
"array-map", "inf", "nan", "list",
"float", "int", "double", "long", "short", "byte",
"boolean", "num", "number",
"make-hierarchy", "derive", "underive", "ancestors",
"descendants", "parents", "isa?", "promise", "create-ns", "future",
"delay",
"aclone", "alength", "aget", "int-array", "identical?", "empty", "identity",
"conj",
"drop-last", "shuffle", "repeatedly", "fnil", "intern",
"println-str", "prn-str", "binding", "aset",
"volatile!", "deliver", "doall", "dorun",
"to-array", "into-array", "vector", "rand", "rand-int",
"rand-nth", "random-sample",
"assoc", "dissoc", "get", "get-in", "update", "assoc-in",
"contains?", "select-keys", "keys", "vals",
"disj", "peek", "pop",
"transduce", "ex-info",
"compare", "subvec",
"require", "eval", "resolve", "random-uuid",
"vreset!", "restart-agent", "with-out-str",
"System/getProperty",
"dosync", "alter"]
if variadic: if variadic:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))" return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))"
else: else:
@@ -2193,6 +2258,10 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
if isLocalVar(symName): if isLocalVar(symName):
return sp & mangleName(symName) return sp & mangleName(symName)
if symName in definedGlobals: if symName in definedGlobals:
# If the global is a user-defined function, emit a cljFn wrapper
# so it can be passed as a first-class value.
if symName in multiArityFns or definedFnArities.hasKey(symName):
return sp & emitFnWrapper(symName)
return sp & mangleName(symName) return sp & mangleName(symName)
return sp & "cljSymbol(\"" & symName & "\")" return sp & "cljSymbol(\"" & symName & "\")"
of ckList: of ckList:
@@ -2201,12 +2270,12 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
# Macro-expand before emitting # Macro-expand before emitting
let expanded = macroexpand(v) let expanded = macroexpand(v)
if expanded == v: if expanded == v:
return emitSpecialForm(v.items, indent) return emitSpecialForm(v.items, indent, needsValue)
return emitExpr(expanded, indent) return emitExpr(expanded, indent, needsValue)
of ckVector: of ckVector:
var parts: seq[string] = @[] var parts: seq[string] = @[]
for item in v.items: for item in v.items:
parts.add(emitExpr(item, indent)) parts.add(emitExpr(item, indent, needsValue = true))
return sp & "cljVector(@[" & parts.join(", ") & "])" return sp & "cljVector(@[" & parts.join(", ") & "])"
of ckMap: of ckMap:
if v.mapKeys.len == 0: if v.mapKeys.len == 0:
@@ -2214,18 +2283,18 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
var keyParts: seq[string] = @[] var keyParts: seq[string] = @[]
var valParts: seq[string] = @[] var valParts: seq[string] = @[]
for i in 0..<v.mapKeys.len: for i in 0..<v.mapKeys.len:
keyParts.add(emitExpr(v.mapKeys[i], indent)) keyParts.add(emitExpr(v.mapKeys[i], indent, needsValue = true))
valParts.add(emitExpr(v.mapVals[i], indent)) valParts.add(emitExpr(v.mapVals[i], indent, needsValue = true))
return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])" return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
of ckSet: of ckSet:
var parts: seq[string] = @[] var parts: seq[string] = @[]
for item in v.setItems: for item in v.setItems:
parts.add(emitExpr(item, indent)) parts.add(emitExpr(item, indent, needsValue = true))
return sp & "cljSet(@[" & parts.join(", ") & "])" return sp & "cljSet(@[" & parts.join(", ") & "])"
of ckFn: of ckFn:
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())" return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())"
of ckAtom: of ckAtom:
return sp & "cljAtom(" & emitExpr(v.atomVal, 0) & ")" return sp & "cljAtom(" & emitExpr(v.atomVal, 0, needsValue = true) & ")"
of ckTransient: of ckTransient:
return sp & "cljTransient(cljNil())" return sp & "cljTransient(cljNil())"
of ckAgent: of ckAgent:
@@ -2320,7 +2389,7 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
let defCode = emitExpr(ef, 0) let defCode = emitExpr(ef, 0)
defs.add(defCode) defs.add(defCode)
else: else:
let code = emitExpr(ef, 2) let code = emitExpr(ef, 2, needsValue = isLast)
let stripped = code.strip() let stripped = code.strip()
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
stripped.startsWith("var ") or stripped.startsWith("while ") or stripped.startsWith("var ") or stripped.startsWith("while ") or
+15 -1
View File
@@ -1,7 +1,7 @@
# Tree-walking interpreter for fast REPL evaluation # Tree-walking interpreter for fast REPL evaluation
# Handles common cases without spawning nim c # Handles common cases without spawning nim c
import strutils, sequtils, tables, algorithm, times, deques import strutils, sequtils, tables, algorithm, times, deques
import types, reader, ai_assist import types, reader, macros, ai_assist
var agentRegistry* = initTable[string, CljVal]() var agentRegistry* = initTable[string, CljVal]()
var agentCounter*: int64 = 0 var agentCounter*: int64 = 0
@@ -228,6 +228,20 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
return EvalResult(ok: true, value: res.value) return EvalResult(ok: true, value: res.value)
return EvalResult(ok: true, value: cljNil()) return EvalResult(ok: true, value: cljNil())
of "macroexpand":
if items.len < 2:
return EvalResult(ok: false, error: "macroexpand requires an expression")
let argRes = evalAst(items[1], env)
if not argRes.ok: return argRes
return EvalResult(ok: true, value: macroexpand(argRes.value))
of "macroexpand-1":
if items.len < 2:
return EvalResult(ok: false, error: "macroexpand-1 requires an expression")
let argRes = evalAst(items[1], env)
if not argRes.ok: return argRes
return EvalResult(ok: true, value: macroexpand1(argRes.value))
of "ai/debug", "ai.debug": of "ai/debug", "ai.debug":
if items.len < 2: if items.len < 2:
return EvalResult(ok: false, error: "ai/debug requires an expression") return EvalResult(ok: false, error: "ai/debug requires an expression")
+28 -28
View File
@@ -149,20 +149,20 @@ proc initMacros*() =
defineMacro("and", proc(args: seq[CljVal]): CljVal = defineMacro("and", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljBool(true) if args.len == 0: return cljBool(true)
if args.len == 1: return args[0] if args.len == 1: return args[0]
var result = args[^1] var acc = args[^1]
for i in countdown(args.len - 2, 0): for i in countdown(args.len - 2, 0):
result = cljList(@[cljSymbol("if"), args[i], result, cljBool(false)]) acc = cljList(@[cljSymbol("if"), args[i], acc, cljBool(false)])
return result return acc
) )
# or # or
defineMacro("or", proc(args: seq[CljVal]): CljVal = defineMacro("or", proc(args: seq[CljVal]): CljVal =
if args.len == 0: return cljNil() if args.len == 0: return cljNil()
if args.len == 1: return args[0] if args.len == 1: return args[0]
var result = args[^1] var acc = args[^1]
for i in countdown(args.len - 2, 0): for i in countdown(args.len - 2, 0):
result = cljList(@[cljSymbol("if"), args[i], args[i], result]) acc = cljList(@[cljSymbol("if"), args[i], args[i], acc])
return result return acc
) )
# when-let # when-let
@@ -242,22 +242,22 @@ proc initMacros*() =
let clauses = args[1..^1] let clauses = args[1..^1]
if clauses.len mod 2 != 0: if clauses.len mod 2 != 0:
raise newException(CatchableError, "cond-> requires pairs of test/expr") raise newException(CatchableError, "cond-> requires pairs of test/expr")
var result = init var acc = init
for i in 0..<(clauses.len div 2): for i in 0..<(clauses.len div 2):
let test = clauses[i * 2] let test = clauses[i * 2]
let expr = clauses[i * 2 + 1] let expr = clauses[i * 2 + 1]
let gs = cljSymbol(gensymName("ct_")) let gs = cljSymbol(gensymName("ct_"))
if expr.kind == ckList and expr.items.len > 0: if expr.kind == ckList and expr.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("if"), test,
cljList(@[expr.items[0], gs] & expr.items[1..^1]), cljList(@[expr.items[0], gs] & expr.items[1..^1]),
gs])]) gs])])
else: else:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("if"), test,
cljList(@[expr, gs]), cljList(@[expr, gs]),
gs])]) gs])])
return result return acc
) )
# cond->> # cond->>
@@ -268,22 +268,22 @@ proc initMacros*() =
let clauses = args[1..^1] let clauses = args[1..^1]
if clauses.len mod 2 != 0: if clauses.len mod 2 != 0:
raise newException(CatchableError, "cond->> requires pairs of test/expr") raise newException(CatchableError, "cond->> requires pairs of test/expr")
var result = init var acc = init
for i in 0..<(clauses.len div 2): for i in 0..<(clauses.len div 2):
let test = clauses[i * 2] let test = clauses[i * 2]
let expr = clauses[i * 2 + 1] let expr = clauses[i * 2 + 1]
let gs = cljSymbol(gensymName("ct_")) let gs = cljSymbol(gensymName("ct_"))
if expr.kind == ckList and expr.items.len > 0: if expr.kind == ckList and expr.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("if"), test,
cljList(expr.items & @[gs]), cljList(expr.items & @[gs]),
gs])]) gs])])
else: else:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("if"), test,
cljList(@[expr, gs]), cljList(@[expr, gs]),
gs])]) gs])])
return result return acc
) )
# doto # doto
@@ -312,10 +312,10 @@ proc initMacros*() =
let init = args[0] let init = args[0]
let name = args[1] let name = args[1]
let body = args[2..^1] let body = args[2..^1]
var result = init var acc = init
for form in body: for form in body:
result = cljList(@[cljSymbol("let"), cljVector(@[name, result]), form]) acc = cljList(@[cljSymbol("let"), cljVector(@[name, acc]), form])
return result return acc
) )
# some-> # some->
@@ -324,21 +324,21 @@ proc initMacros*() =
raise newException(CatchableError, "some-> requires initial value and body") raise newException(CatchableError, "some-> requires initial value and body")
let init = args[0] let init = args[0]
let body = args[1..^1] let body = args[1..^1]
var result = init var acc = init
for form in body: for form in body:
let gs = cljSymbol(gensymName("st_")) let gs = cljSymbol(gensymName("st_"))
let nilCheck = cljList(@[cljSymbol("nil?"), gs]) let nilCheck = cljList(@[cljSymbol("nil?"), gs])
if form.kind == ckList and form.items.len > 0: if form.kind == ckList and form.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), nilCheck, cljList(@[cljSymbol("if"), nilCheck,
cljNil(), cljNil(),
cljList(@[form.items[0], gs] & form.items[1..^1])])]) cljList(@[form.items[0], gs] & form.items[1..^1])])])
else: else:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), nilCheck, cljList(@[cljSymbol("if"), nilCheck,
cljNil(), cljNil(),
cljList(@[form, gs])])]) cljList(@[form, gs])])])
return result return acc
) )
# some->> # some->>
@@ -347,20 +347,20 @@ proc initMacros*() =
raise newException(CatchableError, "some->> requires initial value and body") raise newException(CatchableError, "some->> requires initial value and body")
let init = args[0] let init = args[0]
let body = args[1..^1] let body = args[1..^1]
var result = init var acc = init
for form in body: for form in body:
let gs = cljSymbol(gensymName("st_")) let gs = cljSymbol(gensymName("st_"))
if form.kind == ckList and form.items.len > 0: if form.kind == ckList and form.items.len > 0:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), gs, cljList(@[cljSymbol("if"), gs,
cljList(form.items & @[gs]), cljList(form.items & @[gs]),
cljNil()])]) cljNil()])])
else: else:
result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), acc = cljList(@[cljSymbol("let"), cljVector(@[gs, acc]),
cljList(@[cljSymbol("if"), gs, cljList(@[cljSymbol("if"), gs,
cljList(@[form, gs]), cljList(@[form, gs]),
cljNil()])]) cljNil()])])
return result return acc
) )
# for # for
@@ -465,11 +465,11 @@ proc initMacros*() =
let loopBody = cljList(@[cljSymbol("when"), name] & innerBody & @[recurForm]) let loopBody = cljList(@[cljSymbol("when"), name] & innerBody & @[recurForm])
return cljList(@[cljSymbol("loop"), cljVector(@[name, cljList(@[cljSymbol("first"), seqColl]), gs, cljList(@[cljSymbol("next"), seqColl])]), loopBody]) return cljList(@[cljSymbol("loop"), cljVector(@[name, cljList(@[cljSymbol("first"), seqColl]), gs, cljList(@[cljSymbol("next"), seqColl])]), loopBody])
var result = makeLoop(pairs[^1][0], pairs[^1][1], inner) var acc = makeLoop(pairs[^1][0], pairs[^1][1], inner)
for j in countdown(pairs.len - 2, 0): for j in countdown(pairs.len - 2, 0):
let innerSeq = @[result] let innerSeq = @[result]
result = makeLoop(pairs[j][0], pairs[j][1], innerSeq) acc = makeLoop(pairs[j][0], pairs[j][1], innerSeq)
return result return acc
) )
# dotimes # dotimes
+10 -3
View File
@@ -16,13 +16,18 @@ type
evalEnv*: Env evalEnv*: Env
proc getLibPath(): string = proc getLibPath(): string =
# Check environment variable first
let envPath = getEnv("CLJNIM_LIB_PATH", "")
if envPath.len > 0 and dirExists(envPath):
return envPath
# Check current directory's lib first (project-local libs)
let candidate0 = getCurrentDir() / "lib"
if dirExists(candidate0): return candidate0
let appDir = getAppDir() let appDir = getAppDir()
let candidate1 = appDir / "lib" let candidate1 = appDir / "lib"
let candidate2 = appDir.parentDir / "lib" let candidate2 = appDir.parentDir / "lib"
let candidate3 = getCurrentDir() / "lib"
if dirExists(candidate1): return candidate1 if dirExists(candidate1): return candidate1
if dirExists(candidate2): return candidate2 if dirExists(candidate2): return candidate2
if dirExists(candidate3): return candidate3
return "lib" return "lib"
proc initReplState*(mode: ReplMode): ReplState = proc initReplState*(mode: ReplMode): ReplState =
@@ -150,8 +155,10 @@ proc evalForm*(state: var ReplState, formStr: string): JsonNode =
parsed.items[0].kind == ckSymbol and parsed.items[0].kind == ckSymbol and
parsed.items[0].symName in ["def", "defn"] parsed.items[0].symName in ["def", "defn"]
# Macro-expand before evaluating
let expanded = macroexpand(parsed)
# Try in-memory evaluation first (fast path) # Try in-memory evaluation first (fast path)
let evalResult = evalAst(parsed, state.evalEnv) let evalResult = evalAst(expanded, state.evalEnv)
if evalResult.ok: if evalResult.ok:
let elapsed = (epochTime() - startTime) * 1000 let elapsed = (epochTime() - startTime) * 1000