Compare commits
12 Commits
fb8928e830
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 11fca52282 | |||
| 7cb8aa1ee0 | |||
| ab29c74299 | |||
| b207cc1c77 | |||
| 4b208dbe55 | |||
| 014a192c5d | |||
| 7adace4ee1 | |||
| a1a65f1c27 | |||
| aac39a08ca | |||
| 9a8519df87 | |||
| bc33eab7fc | |||
| 3aaf3c0548 |
+1
-2
@@ -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
|
||||||
|
|||||||
@@ -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)'
|
||||||
|
|||||||
@@ -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*
|
||||||
@@ -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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 е проектиран за програмно взаимодействие.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
+3
-7
@@ -77,16 +77,12 @@ proc pushLeaf[T](node: PVecNode[T], index: int, shift: int, val: seq[T]): PVecNo
|
|||||||
# Push a full leaf (seq of up to 32 values) into the tree at position 'index'
|
# Push a full leaf (seq of up to 32 values) into the tree at position 'index'
|
||||||
result = copyNode(node)
|
result = copyNode(node)
|
||||||
if shift == 0:
|
if shift == 0:
|
||||||
# Should not happen — we always push into internal nodes
|
|
||||||
result = newLeafNode[T](val)
|
result = newLeafNode[T](val)
|
||||||
else:
|
else:
|
||||||
let childIdx = (index shr shift) and BRANCHING_MASK
|
let childIdx = (index shr shift) and BRANCHING_MASK
|
||||||
if childIdx >= result.children.len:
|
# Grow children array and fill any gaps with empty internal nodes
|
||||||
# Need to grow children array
|
while result.children.len <= childIdx:
|
||||||
let oldLen = result.children.len
|
result.children.add(newInternalNode[T]())
|
||||||
result.children.setLen(childIdx + 1)
|
|
||||||
for i in oldLen..<childIdx:
|
|
||||||
result.children[i] = nil
|
|
||||||
|
|
||||||
var child = result.children[childIdx]
|
var child = result.children[childIdx]
|
||||||
if child.isNil:
|
if child.isNil:
|
||||||
|
|||||||
+20
-9
@@ -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)
|
||||||
|
|||||||
+345
-14
@@ -1,9 +1,14 @@
|
|||||||
# Bara Lang — AI-First Compiler
|
# Bara Lang — AI-First Compiler
|
||||||
import os, osproc, strutils, times
|
import os, osproc, strutils, times, tables
|
||||||
import reader, emitter, types, repl, macros, deps, ai_assist, tui
|
import reader, emitter, types, repl, macros, deps, ai_assist, tui, project
|
||||||
|
|
||||||
|
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
|
||||||
@@ -60,7 +65,7 @@ proc extractRequires(forms: seq[CljVal]): seq[(string, string)] =
|
|||||||
walk(form)
|
walk(form)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[string] = @[], libMode: bool = false) =
|
proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[string] = @[], libMode: bool = false, entryProcName: string = "", libs: Table[string, string] = initTable[string, string]()) =
|
||||||
let source = readFile(inputPath)
|
let source = readFile(inputPath)
|
||||||
let forms = reader.readAll(source)
|
let forms = reader.readAll(source)
|
||||||
|
|
||||||
@@ -83,10 +88,17 @@ proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[
|
|||||||
var allForms: seq[CljVal] = @[]
|
var allForms: seq[CljVal] = @[]
|
||||||
var visited: seq[string] = @[]
|
var visited: seq[string] = @[]
|
||||||
var nsAliases: seq[(string, string)] = @[] # (alias, namespace)
|
var nsAliases: seq[(string, string)] = @[] # (alias, namespace)
|
||||||
|
var libImports: seq[string] = @[]
|
||||||
|
|
||||||
proc resolveFile(nsName: string) =
|
proc resolveFile(nsName: string) =
|
||||||
if nsName in visited: return
|
if nsName in visited: return
|
||||||
visited.add(nsName)
|
visited.add(nsName)
|
||||||
|
if libs.hasKey(nsName):
|
||||||
|
# Use pre-compiled lib module instead of inlining
|
||||||
|
let nimModule = libs[nsName]
|
||||||
|
if nimModule notin libImports:
|
||||||
|
libImports.add(nimModule)
|
||||||
|
return
|
||||||
let path = resolveNsToPath(nsName, searchPaths)
|
let path = resolveNsToPath(nsName, searchPaths)
|
||||||
if path.len == 0:
|
if path.len == 0:
|
||||||
stderr.writeLine("Warning: Cannot find file for namespace: " & nsName)
|
stderr.writeLine("Warning: Cannot find file for namespace: " & nsName)
|
||||||
@@ -113,13 +125,37 @@ proc compileFileInternal(inputPath: string, outputPath: string, extraPaths: seq[
|
|||||||
# Set namespace aliases in emitter
|
# Set namespace aliases in emitter
|
||||||
emitter.setNsAliases(nsAliases)
|
emitter.setNsAliases(nsAliases)
|
||||||
|
|
||||||
|
# Set lib prefixes for qualified name resolution
|
||||||
|
var libPrefixes: seq[string] = @[]
|
||||||
|
for (alias, nsName) in nsAliases:
|
||||||
|
if libs.hasKey(nsName):
|
||||||
|
libPrefixes.add(alias)
|
||||||
|
emitter.setLibNsPrefixes(libPrefixes)
|
||||||
|
|
||||||
# Add current file's forms (skip ns declaration)
|
# Add current file's forms (skip ns declaration)
|
||||||
for f in forms:
|
for f in forms:
|
||||||
if not (f.kind == ckList and f.items.len >= 1 and
|
if not (f.kind == ckList and f.items.len >= 1 and
|
||||||
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
|
f.items[0].kind == ckSymbol and f.items[0].symName == "ns"):
|
||||||
allForms.add(f)
|
allForms.add(f)
|
||||||
|
|
||||||
let nimCode = if libMode: emitter.emitProgramLib(allForms) else: emitter.emitProgram(allForms)
|
let oldEntryProc = emitter.emitEntryProcName
|
||||||
|
if entryProcName.len > 0:
|
||||||
|
emitter.emitEntryProcName = entryProcName
|
||||||
|
var nimCode = if libMode: emitter.emitProgramLib(allForms) else: emitter.emitProgram(allForms)
|
||||||
|
emitter.emitEntryProcName = oldEntryProc
|
||||||
|
|
||||||
|
# Insert lib imports into generated Nim code
|
||||||
|
if libImports.len > 0:
|
||||||
|
var lines = nimCode.splitLines()
|
||||||
|
var insertIdx = 0
|
||||||
|
for i, line in lines:
|
||||||
|
if line.startsWith("import "):
|
||||||
|
insertIdx = i + 1
|
||||||
|
for imp in libImports:
|
||||||
|
lines.insert("from " & imp & " import nil", insertIdx)
|
||||||
|
insertIdx.inc
|
||||||
|
nimCode = lines.join("\n")
|
||||||
|
|
||||||
writeFile(outputPath, nimCode)
|
writeFile(outputPath, nimCode)
|
||||||
echo "Generated: ", outputPath
|
echo "Generated: ", outputPath
|
||||||
|
|
||||||
@@ -131,9 +167,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
|
||||||
@@ -146,6 +186,269 @@ proc makeTempDir(): string =
|
|||||||
result = getTempDir() / "cljnim_build_" & $pid & "_" & $ts
|
result = getTempDir() / "cljnim_build_" & $pid & "_" & $ts
|
||||||
createDir(result)
|
createDir(result)
|
||||||
|
|
||||||
|
proc sanitizeNimIdent(name: string): string =
|
||||||
|
## Sanitize a project/bin name to valid Nim module name
|
||||||
|
result = ""
|
||||||
|
for c in name:
|
||||||
|
case c
|
||||||
|
of '-': result.add('_')
|
||||||
|
of 'a'..'z', 'A'..'Z', '0'..'9', '_': result.add(c)
|
||||||
|
else: result.add('_')
|
||||||
|
if result.len == 0:
|
||||||
|
result = "bin"
|
||||||
|
|
||||||
|
proc buildLibs(proj: Project, includeExamples: bool, nimDir: string): Table[string, string] =
|
||||||
|
## Discover and compile all project-local libraries required by bin files.
|
||||||
|
## Returns mapping: namespace -> nim module name
|
||||||
|
result = initTable[string, string]()
|
||||||
|
var entries = proj.bins
|
||||||
|
if includeExamples:
|
||||||
|
entries.add(proj.examples)
|
||||||
|
|
||||||
|
var visited: seq[string] = @[]
|
||||||
|
var queue: seq[string] = @[]
|
||||||
|
let searchPaths = @[proj.rootDir, proj.rootDir / "lib"]
|
||||||
|
|
||||||
|
# Collect all required namespaces from bin files
|
||||||
|
for entry in entries:
|
||||||
|
let inputPath = proj.rootDir / entry.path
|
||||||
|
if not fileExists(inputPath): continue
|
||||||
|
let forms = reader.readAll(readFile(inputPath))
|
||||||
|
let requires = extractRequires(forms)
|
||||||
|
for (nsName, _) in requires:
|
||||||
|
if nsName notin queue:
|
||||||
|
queue.add(nsName)
|
||||||
|
|
||||||
|
# Iteratively process queue, adding transitive dependencies
|
||||||
|
var i = 0
|
||||||
|
while i < queue.len:
|
||||||
|
let nsName = queue[i]
|
||||||
|
i.inc
|
||||||
|
if nsName in visited: continue
|
||||||
|
visited.add(nsName)
|
||||||
|
|
||||||
|
let path = resolveNsToPath(nsName, searchPaths)
|
||||||
|
if path.len == 0 or not path.startsWith(proj.rootDir):
|
||||||
|
continue # External dependency, skip
|
||||||
|
|
||||||
|
let forms = reader.readAll(readFile(path))
|
||||||
|
let requires = extractRequires(forms)
|
||||||
|
for (depNs, _) in requires:
|
||||||
|
if depNs notin queue:
|
||||||
|
queue.add(depNs)
|
||||||
|
|
||||||
|
# Compile libs in dependency order (simple topological sort)
|
||||||
|
var remaining = visited
|
||||||
|
while remaining.len > 0:
|
||||||
|
var madeProgress = false
|
||||||
|
var nextRemaining: seq[string] = @[]
|
||||||
|
|
||||||
|
for nsName in remaining:
|
||||||
|
let path = resolveNsToPath(nsName, searchPaths)
|
||||||
|
if path.len == 0 or not path.startsWith(proj.rootDir):
|
||||||
|
continue
|
||||||
|
|
||||||
|
let forms = reader.readAll(readFile(path))
|
||||||
|
let requires = extractRequires(forms)
|
||||||
|
|
||||||
|
var allDepsReady = true
|
||||||
|
var depLibs = initTable[string, string]()
|
||||||
|
for (depNs, _) in requires:
|
||||||
|
if depNs == nsName: continue
|
||||||
|
if result.hasKey(depNs):
|
||||||
|
depLibs[depNs] = result[depNs]
|
||||||
|
else:
|
||||||
|
let depPath = resolveNsToPath(depNs, searchPaths)
|
||||||
|
if depPath.len > 0 and depPath.startsWith(proj.rootDir):
|
||||||
|
allDepsReady = false
|
||||||
|
break
|
||||||
|
|
||||||
|
if allDepsReady:
|
||||||
|
madeProgress = true
|
||||||
|
let nimName = "lib_" & sanitizeNimIdent(nsName.replace(".", "_").replace("-", "_"))
|
||||||
|
let nimPath = nimDir / nimName & ".nim"
|
||||||
|
if not fileExists(nimPath):
|
||||||
|
echo "Building lib: ", nsName, " → ", nimPath
|
||||||
|
compileFileInternal(path, nimPath, @[], true, "", depLibs)
|
||||||
|
result[nsName] = nimName
|
||||||
|
else:
|
||||||
|
nextRemaining.add(nsName)
|
||||||
|
|
||||||
|
if not madeProgress and nextRemaining.len > 0:
|
||||||
|
# Circular dependency or bug, force compile first remaining
|
||||||
|
let nsName = nextRemaining[0]
|
||||||
|
nextRemaining.delete(0)
|
||||||
|
let path = resolveNsToPath(nsName, searchPaths)
|
||||||
|
if path.len > 0 and path.startsWith(proj.rootDir):
|
||||||
|
let nimName = "lib_" & sanitizeNimIdent(nsName.replace(".", "_").replace("-", "_"))
|
||||||
|
let nimPath = nimDir / nimName & ".nim"
|
||||||
|
if not fileExists(nimPath):
|
||||||
|
echo "Building lib (forced): ", nsName, " → ", nimPath
|
||||||
|
compileFileInternal(path, nimPath, @[], true, "", initTable[string, string]())
|
||||||
|
result[nsName] = nimName
|
||||||
|
|
||||||
|
remaining = nextRemaining
|
||||||
|
|
||||||
|
proc buildBin(proj: Project, bin: project.BinEntry, outputDir: string, nimDir: string, nimcacheDir: string, release: bool, libs: Table[string, string]) =
|
||||||
|
let inputPath = proj.rootDir / bin.path
|
||||||
|
let nimName = "bin_" & sanitizeNimIdent(bin.name)
|
||||||
|
let nimPath = nimDir / nimName & ".nim"
|
||||||
|
let binPath = outputDir / bin.name
|
||||||
|
|
||||||
|
if not fileExists(inputPath):
|
||||||
|
stderr.writeLine("Warning: Bin file not found: " & inputPath)
|
||||||
|
return
|
||||||
|
|
||||||
|
echo "Building bin: ", bin.name, " → ", binPath
|
||||||
|
|
||||||
|
# Set entry proc name so mono can call it later
|
||||||
|
let oldEntryProc = emitter.emitEntryProcName
|
||||||
|
emitter.emitEntryProcName = "clj_main_" & sanitizeNimIdent(bin.name)
|
||||||
|
defer: emitter.emitEntryProcName = oldEntryProc
|
||||||
|
|
||||||
|
compileFileInternal(inputPath, nimPath, @[], false, "", libs)
|
||||||
|
|
||||||
|
let libPath = getLibPath()
|
||||||
|
let compilerLib = getAppDir() / "lib"
|
||||||
|
var cmd = "nim c"
|
||||||
|
if release:
|
||||||
|
cmd.add(" -d:release")
|
||||||
|
if dirExists(compilerLib) and compilerLib != libPath:
|
||||||
|
cmd.add(" --path:" & quoteShell(compilerLib))
|
||||||
|
cmd.add(" --path:" & quoteShell(libPath))
|
||||||
|
cmd.add(" --path:" & quoteShell(nimDir))
|
||||||
|
cmd.add(" --nimcache:" & quoteShell(nimcacheDir))
|
||||||
|
cmd.add(" -o:" & quoteShell(binPath) & " " & quoteShell(nimPath))
|
||||||
|
echo " ", cmd
|
||||||
|
let (output, exitCode) = execCmdEx(cmd)
|
||||||
|
if exitCode != 0:
|
||||||
|
stderr.writeLine("Compilation failed for ", bin.name)
|
||||||
|
stderr.writeLine(output)
|
||||||
|
quit(1)
|
||||||
|
echo " ✓ ", binPath
|
||||||
|
|
||||||
|
proc buildProject(proj: Project, release: bool, includeExamples: bool) =
|
||||||
|
let targetDir = proj.rootDir / "target"
|
||||||
|
let nimDir = targetDir / "nim"
|
||||||
|
let binDir = targetDir / "bin"
|
||||||
|
let nimcacheDir = targetDir / "nimcache"
|
||||||
|
createDir(nimDir)
|
||||||
|
createDir(binDir)
|
||||||
|
createDir(nimcacheDir)
|
||||||
|
|
||||||
|
echo "Building project: ", proj.name, " v", proj.version
|
||||||
|
echo " Root: ", proj.rootDir
|
||||||
|
echo " Bins: ", proj.bins.len
|
||||||
|
|
||||||
|
let libs = buildLibs(proj, includeExamples, nimDir)
|
||||||
|
if libs.len > 0:
|
||||||
|
echo " Libs: ", libs.len
|
||||||
|
|
||||||
|
for bin in proj.bins:
|
||||||
|
buildBin(proj, bin, binDir, nimDir, nimcacheDir, release, libs)
|
||||||
|
|
||||||
|
if includeExamples:
|
||||||
|
echo " Examples: ", proj.examples.len
|
||||||
|
for ex in proj.examples:
|
||||||
|
buildBin(proj, ex, binDir, nimDir, nimcacheDir, release, libs)
|
||||||
|
|
||||||
|
proc buildMono(proj: Project, release: bool, includeExamples: bool) =
|
||||||
|
let targetDir = proj.rootDir / "target"
|
||||||
|
let nimDir = targetDir / "nim"
|
||||||
|
let binDir = targetDir / "bin"
|
||||||
|
let nimcacheDir = targetDir / "nimcache"
|
||||||
|
createDir(nimDir)
|
||||||
|
createDir(binDir)
|
||||||
|
createDir(nimcacheDir)
|
||||||
|
|
||||||
|
var entries: seq[project.BinEntry] = proj.bins
|
||||||
|
if includeExamples:
|
||||||
|
entries.add(proj.examples)
|
||||||
|
|
||||||
|
echo "Building mono binary: ", proj.name
|
||||||
|
echo " Entries: ", entries.len
|
||||||
|
|
||||||
|
let libs = buildLibs(proj, includeExamples, nimDir)
|
||||||
|
if libs.len > 0:
|
||||||
|
echo " Libs: ", libs.len
|
||||||
|
|
||||||
|
# First compile each entry to a Nim module with exported entry proc
|
||||||
|
var imports: seq[string] = @[]
|
||||||
|
var cases: seq[string] = @[]
|
||||||
|
|
||||||
|
for entry in entries:
|
||||||
|
let inputPath = proj.rootDir / entry.path
|
||||||
|
let nimName = "bin_" & sanitizeNimIdent(entry.name)
|
||||||
|
let nimPath = nimDir / nimName & ".nim"
|
||||||
|
|
||||||
|
if not fileExists(inputPath):
|
||||||
|
stderr.writeLine("Warning: Entry file not found: " & inputPath)
|
||||||
|
continue
|
||||||
|
|
||||||
|
echo " Generating module: ", nimName
|
||||||
|
|
||||||
|
let oldEntryProc = emitter.emitEntryProcName
|
||||||
|
emitter.emitEntryProcName = "clj_main_" & sanitizeNimIdent(entry.name)
|
||||||
|
defer: emitter.emitEntryProcName = oldEntryProc
|
||||||
|
|
||||||
|
compileFileInternal(inputPath, nimPath, @[], false, "", libs)
|
||||||
|
imports.add(nimName)
|
||||||
|
cases.add(" of \"" & entry.name & "\": discard " & "clj_main_" & sanitizeNimIdent(entry.name) & "()")
|
||||||
|
|
||||||
|
if imports.len == 0:
|
||||||
|
stderr.writeLine("Error: No valid entries to build")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
# Generate mono wrapper
|
||||||
|
let monoName = sanitizeNimIdent(proj.name)
|
||||||
|
let monoNimPath = nimDir / "mono.nim"
|
||||||
|
var monoCode = "# Generated by Bara Lang — Mono Binary Wrapper\n"
|
||||||
|
monoCode.add("import os\n")
|
||||||
|
monoCode.add("import cljnim_runtime\n")
|
||||||
|
for imp in imports:
|
||||||
|
monoCode.add("import " & imp & "\n")
|
||||||
|
monoCode.add("\nproc main() =\n")
|
||||||
|
monoCode.add(" if paramCount() < 1:\n")
|
||||||
|
monoCode.add(" echo \"Usage: " & proj.name & " <command>\"\n")
|
||||||
|
monoCode.add(" echo \"Commands:\"\n")
|
||||||
|
for entry in entries:
|
||||||
|
if fileExists(proj.rootDir / entry.path):
|
||||||
|
monoCode.add(" echo \" " & entry.name & "\"\n")
|
||||||
|
monoCode.add(" quit(1)\n")
|
||||||
|
monoCode.add(" let cmd = paramStr(1)\n")
|
||||||
|
monoCode.add(" case cmd\n")
|
||||||
|
for c in cases:
|
||||||
|
monoCode.add(c & "\n")
|
||||||
|
monoCode.add(" else:\n")
|
||||||
|
monoCode.add(" echo \"Unknown command: \" & cmd\n")
|
||||||
|
monoCode.add(" quit(1)\n")
|
||||||
|
monoCode.add("\nwhen isMainModule:\n")
|
||||||
|
monoCode.add(" main()\n")
|
||||||
|
|
||||||
|
writeFile(monoNimPath, monoCode)
|
||||||
|
echo " Generated: ", monoNimPath
|
||||||
|
|
||||||
|
# Compile mono wrapper
|
||||||
|
let monoBinPath = binDir / monoName
|
||||||
|
let libPath = getLibPath()
|
||||||
|
let compilerLib = getAppDir() / "lib"
|
||||||
|
var cmd = "nim c"
|
||||||
|
if release:
|
||||||
|
cmd.add(" -d:release")
|
||||||
|
if dirExists(compilerLib) and compilerLib != libPath:
|
||||||
|
cmd.add(" --path:" & quoteShell(compilerLib))
|
||||||
|
cmd.add(" --path:" & quoteShell(libPath))
|
||||||
|
cmd.add(" --path:" & quoteShell(nimDir))
|
||||||
|
cmd.add(" --nimcache:" & quoteShell(nimcacheDir))
|
||||||
|
cmd.add(" -o:" & quoteShell(monoBinPath) & " " & quoteShell(monoNimPath))
|
||||||
|
echo " Compiling mono: ", cmd
|
||||||
|
let (output, exitCode) = execCmdEx(cmd)
|
||||||
|
if exitCode != 0:
|
||||||
|
stderr.writeLine("Compilation failed for mono binary")
|
||||||
|
stderr.writeLine(output)
|
||||||
|
quit(1)
|
||||||
|
echo " ✓ ", monoBinPath
|
||||||
|
|
||||||
proc runFile*(inputPath: string) =
|
proc runFile*(inputPath: string) =
|
||||||
let baseName = inputPath.splitFile.name
|
let baseName = inputPath.splitFile.name
|
||||||
let buildDir = makeTempDir()
|
let buildDir = makeTempDir()
|
||||||
@@ -215,20 +518,30 @@ 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 build [--mono] [--release] [--examples]"
|
||||||
echo " cljnim deps Resolve dependencies from deps.edn"
|
echo " cljnim repl [--json]"
|
||||||
echo " cljnim -e '<code>' Evaluate expression"
|
echo " cljnim deps"
|
||||||
echo " cljnim ai '<description>' Generate Bara Lang code with AI"
|
echo " cljnim -e '<code>'"
|
||||||
echo " cljnim tui Start interactive TUI"
|
echo " cljnim ai '<description>'"
|
||||||
|
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"
|
||||||
@@ -254,6 +567,7 @@ proc main() =
|
|||||||
let nimPath = buildDir / "expr.nim"
|
let nimPath = buildDir / "expr.nim"
|
||||||
let binPath = buildDir / "expr"
|
let binPath = buildDir / "expr"
|
||||||
let forms = reader.readAll(code)
|
let forms = reader.readAll(code)
|
||||||
|
emitter.emitEntryProcName = ""
|
||||||
let nimCode = emitter.emitProgram(forms)
|
let nimCode = emitter.emitProgram(forms)
|
||||||
writeFile(nimPath, nimCode)
|
writeFile(nimPath, nimCode)
|
||||||
let (compileResult, compileOut) = nimCompile(nimPath, binPath)
|
let (compileResult, compileOut) = nimCompile(nimPath, binPath)
|
||||||
@@ -278,6 +592,23 @@ proc main() =
|
|||||||
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
|
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
|
||||||
compileFile(inputPath, outputPath)
|
compileFile(inputPath, outputPath)
|
||||||
|
|
||||||
|
of "build":
|
||||||
|
var monoMode = false
|
||||||
|
var releaseMode = false
|
||||||
|
var includeExamples = false
|
||||||
|
for i in 1..<args.len:
|
||||||
|
if args[i] == "--mono":
|
||||||
|
monoMode = true
|
||||||
|
elif args[i] == "--release":
|
||||||
|
releaseMode = true
|
||||||
|
elif args[i] == "--examples":
|
||||||
|
includeExamples = true
|
||||||
|
let proj = project.loadProject()
|
||||||
|
if monoMode:
|
||||||
|
buildMono(proj, releaseMode, includeExamples)
|
||||||
|
else:
|
||||||
|
buildProject(proj, releaseMode, includeExamples)
|
||||||
|
|
||||||
of "compile-lib":
|
of "compile-lib":
|
||||||
if args.len < 2:
|
if args.len < 2:
|
||||||
stderr.writeLine("Error: Missing input file")
|
stderr.writeLine("Error: Missing input file")
|
||||||
|
|||||||
+28
-7
@@ -277,7 +277,7 @@ proc cljConj*(coll: CljVal, item: CljVal): CljVal =
|
|||||||
of ckVector:
|
of ckVector:
|
||||||
var newItems = coll.listItems
|
var newItems = coll.listItems
|
||||||
newItems.add(item)
|
newItems.add(item)
|
||||||
cljList(newItems)
|
cljVector(newItems)
|
||||||
else:
|
else:
|
||||||
cljList(@[item])
|
cljList(@[item])
|
||||||
|
|
||||||
@@ -315,11 +315,11 @@ proc cljSeq*(v: CljVal): CljVal =
|
|||||||
else: cljNil()
|
else: cljNil()
|
||||||
|
|
||||||
proc cljVec*(v: CljVal): CljVal =
|
proc cljVec*(v: CljVal): CljVal =
|
||||||
if v.isNil: return cljList(@[])
|
if v.isNil: return cljVector(@[])
|
||||||
case v.kind
|
case v.kind
|
||||||
of ckList: cljList(v.listItems)
|
of ckList: cljVector(v.listItems)
|
||||||
of ckVector: v
|
of ckVector: v
|
||||||
else: cljList(@[v])
|
else: cljVector(@[v])
|
||||||
|
|
||||||
proc cljEmpty*(v: CljVal): CljVal =
|
proc cljEmpty*(v: CljVal): CljVal =
|
||||||
cljBool(cljCount(v) == 0)
|
cljBool(cljCount(v) == 0)
|
||||||
@@ -430,18 +430,39 @@ proc cljType*(v: CljVal): CljVal =
|
|||||||
of ckList: cljKeyword("list")
|
of ckList: cljKeyword("list")
|
||||||
of ckVector: cljKeyword("vector")
|
of ckVector: cljKeyword("vector")
|
||||||
of ckMap: cljKeyword("map")
|
of ckMap: cljKeyword("map")
|
||||||
|
of ckSet: cljKeyword("set")
|
||||||
of ckFn: cljKeyword("function")
|
of ckFn: cljKeyword("function")
|
||||||
of ckAtom: cljKeyword("atom")
|
of ckAtom: cljKeyword("atom")
|
||||||
|
of ckTransient: cljKeyword("transient")
|
||||||
|
of ckAgent: cljKeyword("agent")
|
||||||
|
|
||||||
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
|
proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal =
|
||||||
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
|
if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument")
|
||||||
result = args[0]
|
result = args[0]
|
||||||
for i in 1..<args.len:
|
for i in 1..<args.len:
|
||||||
if args[i].kind == ckInt and result.kind == ckInt:
|
let a = args[i]
|
||||||
|
if a.kind == ckInt and result.kind == ckInt:
|
||||||
if isMin:
|
if isMin:
|
||||||
if args[i].intVal < result.intVal: result = args[i]
|
if a.intVal < result.intVal: result = a
|
||||||
else:
|
else:
|
||||||
if args[i].intVal > result.intVal: result = args[i]
|
if a.intVal > result.intVal: result = a
|
||||||
|
elif a.kind == ckFloat and result.kind == ckFloat:
|
||||||
|
if isMin:
|
||||||
|
if a.floatVal < result.floatVal: result = a
|
||||||
|
else:
|
||||||
|
if a.floatVal > result.floatVal: result = a
|
||||||
|
elif a.kind == ckInt and result.kind == ckFloat:
|
||||||
|
let af = a.intVal.float64
|
||||||
|
if isMin:
|
||||||
|
if af < result.floatVal: result = a
|
||||||
|
else:
|
||||||
|
if af > result.floatVal: result = a
|
||||||
|
elif a.kind == ckFloat and result.kind == ckInt:
|
||||||
|
let rf = result.intVal.float64
|
||||||
|
if isMin:
|
||||||
|
if a.floatVal < rf: result = a
|
||||||
|
else:
|
||||||
|
if a.floatVal > rf: result = a
|
||||||
|
|
||||||
proc cljAbs*(v: CljVal): CljVal =
|
proc cljAbs*(v: CljVal): CljVal =
|
||||||
case v.kind
|
case v.kind
|
||||||
|
|||||||
+205
-82
@@ -1,26 +1,103 @@
|
|||||||
# 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
|
||||||
|
|
||||||
var requiredImports* = initHashSet[string]()
|
var requiredImports* = initHashSet[string]()
|
||||||
var emitLibMode* = false
|
var emitLibMode* = false
|
||||||
|
var emitEntryProcName* = ""
|
||||||
var loopStack*: seq[seq[string]] = @[]
|
var loopStack*: seq[seq[string]] = @[]
|
||||||
var nsAliases*: seq[(string, string)] = @[] # (alias, namespace)
|
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
|
||||||
|
|
||||||
|
var libNsPrefixes*: seq[string] = @[]
|
||||||
|
|
||||||
|
proc setLibNsPrefixes*(prefixes: seq[string]) =
|
||||||
|
libNsPrefixes = prefixes
|
||||||
|
|
||||||
|
proc sanitizeNimIdent*(name: string): string
|
||||||
|
|
||||||
|
proc nsToNimModuleName(ns: string): string =
|
||||||
|
result = "lib_"
|
||||||
|
for c in ns:
|
||||||
|
case c
|
||||||
|
of '.', '-': result.add('_')
|
||||||
|
of 'a'..'z', 'A'..'Z', '0'..'9', '_': result.add(c)
|
||||||
|
else: result.add('_')
|
||||||
|
|
||||||
proc resolveNsAlias*(name: string): string =
|
proc resolveNsAlias*(name: string): string =
|
||||||
# Resolve mu/square -> square (strip namespace prefix if alias exists)
|
# Resolve mu/square -> square (strip namespace prefix if alias exists)
|
||||||
let slashIdx = name.find('/')
|
let slashIdx = name.find('/')
|
||||||
@@ -29,13 +106,17 @@ proc resolveNsAlias*(name: string): string =
|
|||||||
let suffix = name[slashIdx+1..^1]
|
let suffix = name[slashIdx+1..^1]
|
||||||
for (alias, ns) in nsAliases:
|
for (alias, ns) in nsAliases:
|
||||||
if prefix == alias:
|
if prefix == alias:
|
||||||
|
if prefix in libNsPrefixes:
|
||||||
|
# Return fully qualified name for lib module symbols
|
||||||
|
return nsToNimModuleName(ns) & ".clj_" & sanitizeNimIdent(suffix)
|
||||||
return suffix
|
return suffix
|
||||||
return name
|
return name
|
||||||
|
|
||||||
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
|
||||||
@@ -68,6 +149,10 @@ proc mangleName*(name: string): string =
|
|||||||
result = "x" & result
|
result = "x" & result
|
||||||
if result.len == 0:
|
if result.len == 0:
|
||||||
result = "val"
|
result = "val"
|
||||||
|
# Avoid conflicts with runtime functions: all-uppercase short names (GET, POST, etc)
|
||||||
|
# get a suffix so they don't collide with cljGet, cljSet, etc.
|
||||||
|
if result.len > 0 and result.allCharsInSet({'A'..'Z', '_'}) and result.len <= 7:
|
||||||
|
result.add("X")
|
||||||
# Nim keywords that would clash
|
# Nim keywords that would clash
|
||||||
let nimKeywords = ["in", "out", "var", "let", "proc", "func", "type", "ref", "ptr",
|
let nimKeywords = ["in", "out", "var", "let", "proc", "func", "type", "ref", "ptr",
|
||||||
"object", "method", "template", "macro", "iterator", "converter",
|
"object", "method", "template", "macro", "iterator", "converter",
|
||||||
@@ -79,8 +164,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 +185,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 +196,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 +499,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 +528,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 +553,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,8 +754,9 @@ 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
|
let exportMarker = if emitLibMode: "*" else: ""
|
||||||
|
return sp & "let " & mangled & exportMarker & " = " & valCode
|
||||||
return sp & "cljNil()"
|
return sp & "cljNil()"
|
||||||
var name = items[1]
|
var name = items[1]
|
||||||
# Strip metadata: (with-meta sym meta) -> sym
|
# Strip metadata: (with-meta sym meta) -> sym
|
||||||
@@ -685,6 +772,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
|
# Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper
|
||||||
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
|
let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and
|
||||||
valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn"
|
valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn"
|
||||||
|
let exportMarker = if emitLibMode: "*" else: ""
|
||||||
if indent == 0:
|
if indent == 0:
|
||||||
if isFnDef:
|
if isFnDef:
|
||||||
# Emit fn as raw proc for def context
|
# Emit fn as raw proc for def context
|
||||||
@@ -692,9 +780,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
emitLibMode = false
|
emitLibMode = false
|
||||||
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 & exportMarker & " = " & valCode
|
||||||
let valCode = emitExpr(items[2], 0)
|
let valCode = emitExpr(items[2], 0, needsValue = true)
|
||||||
return sp & "let " & mangled & " = " & valCode
|
return sp & "let " & mangled & exportMarker & " = " & 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
|
||||||
if isFnDef:
|
if isFnDef:
|
||||||
@@ -702,9 +790,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
emitLibMode = false
|
emitLibMode = false
|
||||||
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 & exportMarker & " = " & 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 & exportMarker & " = " & valCode.strip() & "\n" & sp & mangled
|
||||||
|
|
||||||
of "defn":
|
of "defn":
|
||||||
if items.len < 3:
|
if items.len < 3:
|
||||||
@@ -805,6 +893,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 +911,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:
|
||||||
@@ -831,7 +921,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
else:
|
else:
|
||||||
bodyCode = emitBlock(body, indent + 1)
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
popScope()
|
popScope()
|
||||||
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
var result = sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||||
|
if emitLibMode and paramNames.len > 0 and not (name.symName in multiArityFns):
|
||||||
|
var wrapperArgs: seq[string] = @[]
|
||||||
|
for i in 0..<paramNames.len:
|
||||||
|
wrapperArgs.add("args[" & $i & "]")
|
||||||
|
result.add("\n" & sp & "proc " & procName & exportMarker & "(args: seq[CljVal]): CljVal =\n")
|
||||||
|
result.add(indentStr(indent + 1) & procName & "(" & wrapperArgs.join(", ") & ")\n")
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
var bodyCode = ""
|
var bodyCode = ""
|
||||||
if body.len == 1:
|
if body.len == 1:
|
||||||
@@ -893,6 +990,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 +1008,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 +1106,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 +1276,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 +1341,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 +1569,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 +1646,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 +1679,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 +1839,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 +2079,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 +2167,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 +2187,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",
|
||||||
@@ -2091,6 +2213,10 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
|||||||
var callOp = op
|
var callOp = op
|
||||||
if callOp.endsWith("."):
|
if callOp.endsWith("."):
|
||||||
callOp = callOp[0..^2]
|
callOp = callOp[0..^2]
|
||||||
|
let baseOp = if callOp.contains("/"): callOp.split("/")[1] else: callOp
|
||||||
|
let resolvedOp = resolveNsAlias(callOp)
|
||||||
|
if resolvedOp.contains("."):
|
||||||
|
return sp & resolvedOp & "(@[" & args.join(", ") & "])"
|
||||||
let mangled = mangleName(callOp)
|
let mangled = mangleName(callOp)
|
||||||
# Multi-arity function: wrap args in seq
|
# Multi-arity function: wrap args in seq
|
||||||
if callOp in multiArityFns:
|
if callOp in multiArityFns:
|
||||||
@@ -2131,7 +2257,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:
|
||||||
@@ -2157,35 +2283,13 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
|||||||
return sp & "cljKeyword(\"" & v.kwName & "\")"
|
return sp & "cljKeyword(\"" & v.kwName & "\")"
|
||||||
of ckSymbol:
|
of ckSymbol:
|
||||||
let symName = resolveNsAlias(v.symName)
|
let symName = resolveNsAlias(v.symName)
|
||||||
|
# If symName contains a dot, it's a fully qualified lib module reference
|
||||||
|
if symName.contains("."):
|
||||||
|
return sp & symName
|
||||||
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 +2297,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 +2309,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 +2322,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:
|
||||||
@@ -2286,6 +2394,13 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
|||||||
scopeStack = @[]
|
scopeStack = @[]
|
||||||
pushScope()
|
pushScope()
|
||||||
requiredImports = initHashSet[string]()
|
requiredImports = initHashSet[string]()
|
||||||
|
loopStack = @[]
|
||||||
|
loopResultVar = ""
|
||||||
|
nsAliases = @[]
|
||||||
|
libNsPrefixes = @[]
|
||||||
|
definedGlobals = initHashSet[string]()
|
||||||
|
definedFnArities = initTable[string, int]()
|
||||||
|
multiArityFns = initHashSet[string]()
|
||||||
var headerLines: seq[string] = @[
|
var headerLines: seq[string] = @[
|
||||||
"# Generated by Bara Lang",
|
"# Generated by Bara Lang",
|
||||||
"import cljnim_runtime",
|
"import cljnim_runtime",
|
||||||
@@ -2320,7 +2435,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
|
||||||
@@ -2374,9 +2489,17 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
|||||||
for form in mainForms:
|
for form in mainForms:
|
||||||
lines.add(form)
|
lines.add(form)
|
||||||
else:
|
else:
|
||||||
lines.add("when isMainModule:")
|
if emitEntryProcName.len > 0:
|
||||||
for form in mainForms:
|
lines.add("proc " & emitEntryProcName & "*(args: seq[CljVal] = @[]): CljVal =")
|
||||||
lines.add(form)
|
for form in mainForms:
|
||||||
|
lines.add(form)
|
||||||
|
lines.add("")
|
||||||
|
lines.add("when isMainModule:")
|
||||||
|
lines.add(" discard " & emitEntryProcName & "()")
|
||||||
|
else:
|
||||||
|
lines.add("when isMainModule:")
|
||||||
|
for form in mainForms:
|
||||||
|
lines.add(form)
|
||||||
|
|
||||||
return lines.join("\n") & "\n"
|
return lines.join("\n") & "\n"
|
||||||
|
|
||||||
|
|||||||
+31
-14
@@ -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")
|
||||||
@@ -711,9 +725,10 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
coll = args[2]
|
coll = args[2]
|
||||||
else:
|
else:
|
||||||
coll = args[1]
|
coll = args[1]
|
||||||
if coll.kind in {ckList, ckVector} and coll.items.len > 0:
|
if coll.kind in {ckList, ckVector}:
|
||||||
|
if coll.items.len == 0:
|
||||||
|
return EvalResult(ok: false, error: "reduce of empty collection with no initial value")
|
||||||
acc = coll.items[0]
|
acc = coll.items[0]
|
||||||
# reduce rest
|
|
||||||
var res = acc
|
var res = acc
|
||||||
for i in 1..<coll.items.len:
|
for i in 1..<coll.items.len:
|
||||||
let callItems = @[fn, res, coll.items[i]]
|
let callItems = @[fn, res, coll.items[i]]
|
||||||
@@ -906,12 +921,6 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
of ckAgent: "agent"
|
of ckAgent: "agent"
|
||||||
return EvalResult(ok: true, value: cljKeyword(typeName))
|
return EvalResult(ok: true, value: cljKeyword(typeName))
|
||||||
|
|
||||||
of "not":
|
|
||||||
numArgs(1)
|
|
||||||
let isFalsy = args[0].kind == ckNil or
|
|
||||||
(args[0].kind == ckBool and not args[0].boolVal)
|
|
||||||
return EvalResult(ok: true, value: cljBool(isFalsy))
|
|
||||||
|
|
||||||
of "abs":
|
of "abs":
|
||||||
numArgs(1)
|
numArgs(1)
|
||||||
if args[0].kind == ckInt:
|
if args[0].kind == ckInt:
|
||||||
@@ -939,7 +948,7 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
if args[0].kind == ckInt and args[1].kind == ckInt:
|
if args[0].kind == ckInt and args[1].kind == ckInt:
|
||||||
if args[1].intVal == 0:
|
if args[1].intVal == 0:
|
||||||
return EvalResult(ok: false, error: "Division by zero")
|
return EvalResult(ok: false, error: "Division by zero")
|
||||||
return EvalResult(ok: true, value: cljInt(args[0].intVal mod args[1].intVal))
|
return EvalResult(ok: true, value: cljInt(args[0].intVal - (args[0].intVal div args[1].intVal) * args[1].intVal))
|
||||||
return EvalResult(ok: false, error: "rem requires integers")
|
return EvalResult(ok: false, error: "rem requires integers")
|
||||||
|
|
||||||
of "min":
|
of "min":
|
||||||
@@ -970,9 +979,16 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
if args.len == 3 and args[0].kind == ckInt and args[1].kind == ckInt and args[2].kind == ckInt:
|
if args.len == 3 and args[0].kind == ckInt and args[1].kind == ckInt and args[2].kind == ckInt:
|
||||||
var items: seq[CljVal] = @[]
|
var items: seq[CljVal] = @[]
|
||||||
var i = args[0].intVal
|
var i = args[0].intVal
|
||||||
while i < args[1].intVal:
|
let step = args[2].intVal
|
||||||
items.add(cljInt(i))
|
let endVal = args[1].intVal
|
||||||
i += args[2].intVal
|
if step > 0:
|
||||||
|
while i < endVal:
|
||||||
|
items.add(cljInt(i))
|
||||||
|
i += step
|
||||||
|
elif step < 0:
|
||||||
|
while i > endVal:
|
||||||
|
items.add(cljInt(i))
|
||||||
|
i += step
|
||||||
return EvalResult(ok: true, value: cljList(items))
|
return EvalResult(ok: true, value: cljList(items))
|
||||||
return EvalResult(ok: false, error: "range requires integers")
|
return EvalResult(ok: false, error: "range requires integers")
|
||||||
|
|
||||||
@@ -1118,7 +1134,8 @@ proc evalList(items: seq[CljVal], env: Env): EvalResult =
|
|||||||
let currentVal = atomRegistry[args[0].strVal]
|
let currentVal = atomRegistry[args[0].strVal]
|
||||||
var callItems = @[fn, currentVal]
|
var callItems = @[fn, currentVal]
|
||||||
if args.len > 2:
|
if args.len > 2:
|
||||||
callItems.add(args[2..^1])
|
for extra in args[2..^1]:
|
||||||
|
callItems.add(extra)
|
||||||
let callRes = evalList(callItems, env)
|
let callRes = evalList(callItems, env)
|
||||||
if not callRes.ok: return callRes
|
if not callRes.ok: return callRes
|
||||||
atomRegistry[args[0].strVal] = callRes.value
|
atomRegistry[args[0].strVal] = callRes.value
|
||||||
|
|||||||
+37
-35
@@ -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
|
||||||
@@ -428,8 +428,7 @@ proc initMacros*() =
|
|||||||
if whileExpr != nil:
|
if whileExpr != nil:
|
||||||
let gs = cljSymbol(gensymName("dw_"))
|
let gs = cljSymbol(gensymName("dw_"))
|
||||||
b = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
b = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||||
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])]),
|
cljList(@[cljSymbol("when"), whileExpr] & b & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
|
||||||
return cljList(@[cljSymbol("do")] & b)
|
return cljList(@[cljSymbol("do")] & b)
|
||||||
|
|
||||||
# Build nested doseq for multiple pairs, innermost gets the body with modifiers
|
# Build nested doseq for multiple pairs, innermost gets the body with modifiers
|
||||||
@@ -438,11 +437,10 @@ proc initMacros*() =
|
|||||||
inner = @[cljList(@[cljSymbol("let"), cljVector(letBinds)] & inner)]
|
inner = @[cljList(@[cljSymbol("let"), cljVector(letBinds)] & inner)]
|
||||||
if whenExpr != nil:
|
if whenExpr != nil:
|
||||||
inner = @[cljList(@[cljSymbol("when"), whenExpr] & inner)]
|
inner = @[cljList(@[cljSymbol("when"), whenExpr] & inner)]
|
||||||
if whileExpr != nil:
|
if whileExpr != nil:
|
||||||
let gs = cljSymbol(gensymName("dw_"))
|
let gs = cljSymbol(gensymName("dw_"))
|
||||||
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
inner = @[cljList(@[cljSymbol("loop"), cljVector(@[gs, cljBool(true)]),
|
||||||
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])]),
|
cljList(@[cljSymbol("when"), whileExpr] & inner & @[cljList(@[cljSymbol("recur"), gs])])])]
|
||||||
cljList(@[cljSymbol("when"), cljBool(false), cljNil()])])]
|
|
||||||
|
|
||||||
proc makeLoop(name: CljVal, coll: CljVal, innerBody: seq[CljVal]): CljVal =
|
proc makeLoop(name: CljVal, coll: CljVal, innerBody: seq[CljVal]): CljVal =
|
||||||
if name.kind == ckVector:
|
if name.kind == ckVector:
|
||||||
@@ -465,11 +463,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
|
||||||
@@ -606,5 +604,9 @@ proc initMacros*() =
|
|||||||
return cljList(@[cljSymbol("do")] & body)
|
return cljList(@[cljSymbol("do")] & body)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var macrosInitialized = false
|
||||||
|
|
||||||
proc initBuiltinMacros*() =
|
proc initBuiltinMacros*() =
|
||||||
|
if macrosInitialized: return
|
||||||
initMacros()
|
initMacros()
|
||||||
|
macrosInitialized = true
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# Bara Lang Project Manifest Reader
|
||||||
|
# Reads bara.edn (Clojure/EDN format) for project configuration
|
||||||
|
import os
|
||||||
|
import types, reader
|
||||||
|
|
||||||
|
type
|
||||||
|
BinEntry* = object
|
||||||
|
name*: string
|
||||||
|
path*: string
|
||||||
|
|
||||||
|
LibEntry* = object
|
||||||
|
name*: string
|
||||||
|
path*: string
|
||||||
|
|
||||||
|
Project* = object
|
||||||
|
name*: string
|
||||||
|
version*: string
|
||||||
|
rootDir*: string
|
||||||
|
lib*: LibEntry
|
||||||
|
bins*: seq[BinEntry]
|
||||||
|
examples*: seq[BinEntry]
|
||||||
|
|
||||||
|
proc findProjectRoot*(startDir: string = getCurrentDir()): string =
|
||||||
|
## Walk up from startDir looking for bara.edn
|
||||||
|
var dir = absolutePath(startDir)
|
||||||
|
while true:
|
||||||
|
let baraEdn = dir / "bara.edn"
|
||||||
|
if fileExists(baraEdn):
|
||||||
|
return dir
|
||||||
|
let parent = parentDir(dir)
|
||||||
|
if parent == dir:
|
||||||
|
break
|
||||||
|
dir = parent
|
||||||
|
return ""
|
||||||
|
|
||||||
|
proc extractMapString(form: CljVal, key: string, defaultVal: string = ""): string =
|
||||||
|
if form.kind != ckMap: return defaultVal
|
||||||
|
for i in 0..<form.mapKeys.len:
|
||||||
|
let k = form.mapKeys[i]
|
||||||
|
if (k.kind == ckKeyword and k.kwName == key) or (k.kind == ckSymbol and k.symName == key):
|
||||||
|
let v = form.mapVals[i]
|
||||||
|
if v.kind == ckString: return v.strVal
|
||||||
|
return defaultVal
|
||||||
|
|
||||||
|
proc extractMapVector(form: CljVal, key: string): seq[CljVal] =
|
||||||
|
if form.kind != ckMap: return @[]
|
||||||
|
for i in 0..<form.mapKeys.len:
|
||||||
|
let k = form.mapKeys[i]
|
||||||
|
if (k.kind == ckKeyword and k.kwName == key) or (k.kind == ckSymbol and k.symName == key):
|
||||||
|
let v = form.mapVals[i]
|
||||||
|
if v.kind == ckVector: return v.items
|
||||||
|
if v.kind == ckList: return v.items
|
||||||
|
return @[]
|
||||||
|
|
||||||
|
proc parseBinEntry(form: CljVal): BinEntry =
|
||||||
|
result.name = extractMapString(form, "name")
|
||||||
|
result.path = extractMapString(form, "path")
|
||||||
|
|
||||||
|
proc parseLibEntry(form: CljVal): LibEntry =
|
||||||
|
result.name = extractMapString(form, "name")
|
||||||
|
result.path = extractMapString(form, "path")
|
||||||
|
|
||||||
|
proc loadProject*(dir: string = getCurrentDir()): Project =
|
||||||
|
let root = findProjectRoot(dir)
|
||||||
|
if root.len == 0:
|
||||||
|
raise newException(ValueError, "bara.edn not found in " & dir & " or parent directories")
|
||||||
|
|
||||||
|
let path = root / "bara.edn"
|
||||||
|
let source = readFile(path)
|
||||||
|
let forms = reader.readAll(source)
|
||||||
|
if forms.len == 0:
|
||||||
|
raise newException(ValueError, "Empty bara.edn")
|
||||||
|
|
||||||
|
let map = forms[0]
|
||||||
|
if map.kind != ckMap:
|
||||||
|
raise newException(ValueError, "bara.edn must contain a map at top level")
|
||||||
|
|
||||||
|
result.rootDir = root
|
||||||
|
result.name = extractMapString(map, "name", "unknown")
|
||||||
|
result.version = extractMapString(map, "version", "0.1.0")
|
||||||
|
|
||||||
|
let libVal = extractMapVector(map, "lib")
|
||||||
|
if libVal.len > 0:
|
||||||
|
result.lib = parseLibEntry(libVal[0])
|
||||||
|
elif true:
|
||||||
|
# Also check for single :lib map (not vector)
|
||||||
|
for i in 0..<map.mapKeys.len:
|
||||||
|
let k = map.mapKeys[i]
|
||||||
|
if (k.kind == ckKeyword and k.kwName == "lib") or (k.kind == ckSymbol and k.symName == "lib"):
|
||||||
|
let v = map.mapVals[i]
|
||||||
|
if v.kind == ckMap:
|
||||||
|
result.lib = parseLibEntry(v)
|
||||||
|
break
|
||||||
|
|
||||||
|
for item in extractMapVector(map, "bins"):
|
||||||
|
result.bins.add(parseBinEntry(item))
|
||||||
|
|
||||||
|
for item in extractMapVector(map, "examples"):
|
||||||
|
result.examples.add(parseBinEntry(item))
|
||||||
+17
-3
@@ -227,6 +227,20 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
except CatchableError:
|
except CatchableError:
|
||||||
return cljSymbol(tok)
|
return cljSymbol(tok)
|
||||||
|
|
||||||
|
# Check for ratio literal like 1/5, -1/5, 3/4
|
||||||
|
let slashPos = numTok.find('/')
|
||||||
|
if slashPos > 0 and slashPos < numTok.len - 1:
|
||||||
|
let numerStr = numTok[0..<slashPos]
|
||||||
|
let denomStr = numTok[slashPos+1..^1]
|
||||||
|
try:
|
||||||
|
let numer = parseInt(numerStr)
|
||||||
|
let denom = parseInt(denomStr)
|
||||||
|
if denom != 0:
|
||||||
|
return cljList(@[cljSymbol("/"), cljInt(numer.int64), cljInt(denom.int64)])
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
return cljSymbol(tok)
|
||||||
|
|
||||||
# number
|
# number
|
||||||
var isFloat = false
|
var isFloat = false
|
||||||
var isNumber = true
|
var isNumber = true
|
||||||
@@ -252,8 +266,8 @@ proc readAtom(s: string, i: var int): CljVal =
|
|||||||
if j + 1 < numTok.len and (numTok[j+1] == '-' or numTok[j+1] == '+'):
|
if j + 1 < numTok.len and (numTok[j+1] == '-' or numTok[j+1] == '+'):
|
||||||
discard # skip exponent sign
|
discard # skip exponent sign
|
||||||
elif numTok[j] notin Digits:
|
elif numTok[j] notin Digits:
|
||||||
if sawExp and (tok[j] == '-' or tok[j] == '+'):
|
if sawExp and (numTok[j] == '-' or numTok[j] == '+'):
|
||||||
if j > 0 and (tok[j-1] == 'e' or tok[j-1] == 'E'):
|
if j > 0 and (numTok[j-1] == 'e' or numTok[j-1] == 'E'):
|
||||||
continue
|
continue
|
||||||
isNumber = false
|
isNumber = false
|
||||||
break
|
break
|
||||||
@@ -366,7 +380,7 @@ proc readSet(s: string, i: var int): CljVal =
|
|||||||
let form = readForm(s, i)
|
let form = readForm(s, i)
|
||||||
if form != nil:
|
if form != nil:
|
||||||
items.add(form)
|
items.add(form)
|
||||||
return cljList(@[cljSymbol("set"), cljVector(items)])
|
return cljSet(items)
|
||||||
|
|
||||||
proc readDispatch(s: string, i: var int): CljVal =
|
proc readDispatch(s: string, i: var int): CljVal =
|
||||||
inc i # skip '#'
|
inc i # skip '#'
|
||||||
|
|||||||
+18
-5
@@ -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
|
||||||
|
|
||||||
@@ -521,8 +528,14 @@ proc handleClient(state: var ReplState, client: Socket) =
|
|||||||
client.send($(%*{ "status": "error", "error": { "type": "repl/unknown-op", "message": "Unknown op: " & op } }) & "\n")
|
client.send($(%*{ "status": "error", "error": { "type": "repl/unknown-op", "message": "Unknown op: " & op } }) & "\n")
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
except:
|
except CatchableError as e:
|
||||||
client.send($(%*{ "status": "error", "error": { "type": "repl/protocol-error", "message": "Protocol error" } }) & "\n")
|
client.send($(%*{ "status": "error", "error": { "type": "repl/protocol-error", "message": e.msg } }) & "\n")
|
||||||
|
break
|
||||||
|
except ValueError as e:
|
||||||
|
client.send($(%*{ "status": "error", "error": { "type": "repl/parse-error", "message": e.msg } }) & "\n")
|
||||||
|
break
|
||||||
|
except OSError as e:
|
||||||
|
client.send($(%*{ "status": "error", "error": { "type": "repl/io-error", "message": e.msg } }) & "\n")
|
||||||
break
|
break
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user