Initial commit
This commit is contained in:
+169
@@ -0,0 +1,169 @@
|
|||||||
|
# Архитектура: Истински Clojure върху Nim
|
||||||
|
|
||||||
|
## Философия
|
||||||
|
|
||||||
|
Това не е "Clojure-подобен" език. Това е **Clojure диалект**, който компилира към Nim.
|
||||||
|
Макросите, структурите от данни и семантиката са съвместими с Clojure (JVM).
|
||||||
|
|
||||||
|
### Защо истински Clojure?
|
||||||
|
|
||||||
|
| Аспект | Clojure-inspired Lisp | Истински Clojure диалект |
|
||||||
|
|---|---|---|
|
||||||
|
| **Библиотеки** | Пишеш всичко на ново | Можеш да пренесеш `clojure.core`, `clojure.string`, `clojure.set` |
|
||||||
|
| **Разработчици** | Учат нов език | Всеки Clojure програмист може да го ползва веднага |
|
||||||
|
| **Инструменти** | Собствени | Работи с `clojure-mode`, CIDER, Calva (с някои адаптации) |
|
||||||
|
| **Финтех/Indустрия** | Не е опция | Може да се ползва за реални проекти |
|
||||||
|
|
||||||
|
### Който го е правил успешно
|
||||||
|
|
||||||
|
- **ClojureScript** — компилира Clojure до JS. Използва Google Closure. Пълна съвместимост.
|
||||||
|
- **Babashka** — GraalVM native image на Clojure. Бърз старт, скриптове, портативен.
|
||||||
|
- **Ferret** — Clojure → C++11 за embedded системи. Работи на 2KB RAM.
|
||||||
|
- **ClojErl** — Clojure върху BEAM. Жертва част от performance заради семантиката.
|
||||||
|
|
||||||
|
## Архитектура на Компилатора
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ .clj Файл │ ← Стандартен Clojure код
|
||||||
|
└────────┬────────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Reader │ ← EDN парсер, Clojure symbols, quoted forms
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Macro │ ← defmacro, syntax-quote, unquote, unquote-splicing
|
||||||
|
│ Expansion│ (работи върху Clojure структури)
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Analyzer│ ← Специални форми, locals, recur targets,
|
||||||
|
│ │ closures, type hints
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Emitter │ ← Генерира Nim AST (не Nim текст!)
|
||||||
|
│ (Nim) │ Използва Nim макроси за компилация
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Nim │ ← C код, после машинен код
|
||||||
|
│Compiler │ оптимизация, tree-shaking, LTO
|
||||||
|
└────┬────┘
|
||||||
|
│
|
||||||
|
┌────▼────┐
|
||||||
|
│ Бинарен │ ← Единичен файл, < 10MB, бърз старт
|
||||||
|
│ Файл │
|
||||||
|
└─────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ключови Решения
|
||||||
|
|
||||||
|
### 1. Компилатор, не Интерпретатор
|
||||||
|
|
||||||
|
Изборът е **AOT компилатор**, подобно на ClojureScript. Няма да пишем интерпретатор в Nim.
|
||||||
|
|
||||||
|
- **Предимство:** Бърз runtime, малък footprint, компилира до C.
|
||||||
|
- **Недостатък:** REPL е по-труден (но не невъзможен — виж ClojureScript self-hosted).
|
||||||
|
- **Урок от calcit-runner:** Те започнаха с интерпретатор в Nim и се провалиха. Ние директно компилираме.
|
||||||
|
|
||||||
|
### 2. Clojure Макроси на Clojure Структури
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Това трябва да работи без промяна:
|
||||||
|
(defmacro unless [condition & body]
|
||||||
|
`(if (not ~condition)
|
||||||
|
(do ~@body)))
|
||||||
|
|
||||||
|
(unless false
|
||||||
|
(println "Работи!"))
|
||||||
|
```
|
||||||
|
|
||||||
|
Макросите се разширяват **преди** да стигнем до Nim. Nim никога не вижда Clojure макрос.
|
||||||
|
|
||||||
|
### 3. Persistent Структури от Данни в Nim
|
||||||
|
|
||||||
|
Clojure's Hash Array Mapped Trie (HAMT) трябва да се имплементира в Nim:
|
||||||
|
|
||||||
|
```nim
|
||||||
|
# Nim runtime за Clojure Vector
|
||||||
|
type
|
||||||
|
NodeKind = enum nkLeaf, nkInternal
|
||||||
|
Node[T] = ref object
|
||||||
|
case kind: NodeKind
|
||||||
|
of nkLeaf: values: seq[T]
|
||||||
|
of nkInternal: children: array[32, Node[T]]
|
||||||
|
|
||||||
|
PersistentVector[T] = object
|
||||||
|
root: Node[T]
|
||||||
|
tail: seq[T]
|
||||||
|
count: int
|
||||||
|
```
|
||||||
|
|
||||||
|
- `conj`, `assoc`, `dissoc` — O(log₃₂ n)
|
||||||
|
- `nth`, `get` — O(log₃₂ n)
|
||||||
|
- `count` — O(1)
|
||||||
|
|
||||||
|
### 4. Nim Interop (вместо Java Interop)
|
||||||
|
|
||||||
|
Тъй като нямаме JVM, interop-ът е с Nim:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Извикване на Nim функция
|
||||||
|
(nim/strutils.join ", " ["a" "b" "c"])
|
||||||
|
|
||||||
|
;; Достъп до Nim тип
|
||||||
|
(nim/times.DateTime)
|
||||||
|
|
||||||
|
;; FFI към C библиотека
|
||||||
|
(nim/ffi.import "math.h" :functions [["sin" :double [:double]]])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Симулация на Clojure Runtime
|
||||||
|
|
||||||
|
| Clojure JVM | Нашата Имплементация |
|
||||||
|
|---|---|
|
||||||
|
| `Var` | Глобални Nim променливи с atomic CAS |
|
||||||
|
| `Namespace` | Nim модул + хеш таблица със символи |
|
||||||
|
| `Atom` | `ref` тип с compare-and-swap |
|
||||||
|
| `Agent` | Channel + Worker thread |
|
||||||
|
| `Ref/STM` | Software transactional memory в Nim |
|
||||||
|
| `Multimethod` | Диспач таблица + `isa?` проверка |
|
||||||
|
| `Protocol` | Nim concepts + dispatch таблица |
|
||||||
|
|
||||||
|
## Фази с Индустриален Фокус
|
||||||
|
|
||||||
|
### Phase 1: Компилатор (не REPL!)
|
||||||
|
Фокус: AOT компилация на прости Clojure файлове към Nim.
|
||||||
|
|
||||||
|
### Phase 2: Core Библиотека
|
||||||
|
Фокус: Пренасяне на `clojure.core` функции. Тестове с реални Clojure тестове.
|
||||||
|
|
||||||
|
### Phase 3: Nim Interop + FFI
|
||||||
|
Фокус: Извикване на Nim/C библиотеки. Това е нашето "убийствено" предимство.
|
||||||
|
|
||||||
|
### Phase 4: REPL (Self-hosted)
|
||||||
|
Фокус: REPL като ClojureScript (компилира в паметта).
|
||||||
|
|
||||||
|
### Phase 5: Инструменти
|
||||||
|
Фокус: nREPL съвместимост, LSP, пакетен мениджър (Nimble интеграция).
|
||||||
|
|
||||||
|
## Изисквания за Съвместимост
|
||||||
|
|
||||||
|
- `clojure.core` namespace трябва да е наличен
|
||||||
|
- `defn`, `let`, `if`, `do`, `recur`, `loop` — точна семантика
|
||||||
|
- `->`, `->>`, `cond->` — макроси
|
||||||
|
- Persistent Vector, Map, Set — стандартни операции
|
||||||
|
- Keywords (`:name`) и Symbols (`'sym`) — interned
|
||||||
|
|
||||||
|
## Разлики от JVM Clojure (приемливи)
|
||||||
|
|
||||||
|
| JVM Clojure | Clojure/Nim |
|
||||||
|
|---|---|
|
||||||
|
| Java interop | Nim interop |
|
||||||
|
| JVM threads | Nim threads (no GIL!) |
|
||||||
|
| Dynamic classloading | AOT компилация |
|
||||||
|
| `gen-class` | `gen-nim` (генериране на Nim типове) |
|
||||||
|
| Reflection | Compile-time метаданни |
|
||||||
|
| Big ecosystem | Достъп до Nim + C ecosystem |
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.PHONY: build test clean run-example
|
||||||
|
|
||||||
|
NIMCACHE = nimcache
|
||||||
|
|
||||||
|
build:
|
||||||
|
nim c -o:cljnim src/cljnim.nim
|
||||||
|
|
||||||
|
test:
|
||||||
|
nim c -r tests/test_reader.nim
|
||||||
|
|
||||||
|
run-example: build
|
||||||
|
./cljnim run examples/hello.clj
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f cljnim
|
||||||
|
rm -rf $(NIMCACHE)
|
||||||
|
find . -name "*_generated.nim" -delete
|
||||||
|
find . -name "*_generated" -delete
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Clojure/Nim
|
||||||
|
|
||||||
|
> Реализация на Clojure върху Nim-lang
|
||||||
|
|
||||||
|
## Какво е това?
|
||||||
|
|
||||||
|
Clojure/Nim е проект, целящ да предостави Clojure език и runtime върху Nim. Вдъхновен е от ClojureScript и Babashka, но вместо JavaScript или JVM, целта е компилация към Nim код и използване на Nim runtime.
|
||||||
|
|
||||||
|
## Защо?
|
||||||
|
|
||||||
|
- **Единен AST**: Nim предлага мощна макро система и AST манипулация
|
||||||
|
- **Производителност**: Компилиран към C, Nim дава C-подобна скорост с Lisp-удобство
|
||||||
|
- **Размер**: Минимален runtime footprint в сравнение с JVM
|
||||||
|
- **Метапрограмиране**: Съчетание на Clojure макроси и Nim макроси
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
Clojure Изходен Код (.clj)
|
||||||
|
↓
|
||||||
|
Clojure Reader (Clojure → Clojure Data)
|
||||||
|
↓
|
||||||
|
Макро Разширение (Clojure Macros)
|
||||||
|
↓
|
||||||
|
AST Транслатор (Clojure AST → Nim AST)
|
||||||
|
↓
|
||||||
|
Nim Компилатор
|
||||||
|
↓
|
||||||
|
C Код → Машинен Код
|
||||||
|
```
|
||||||
|
|
||||||
|
## Статус
|
||||||
|
|
||||||
|
> ⚠️ **Ранен етап** — Проектът е във фаза на планиране и базова имплементация.
|
||||||
|
|
||||||
|
Виж [ROADMAP.md](ROADMAP.md) за текущия прогрес.
|
||||||
|
|
||||||
|
## Бърз Старт
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Клониране
|
||||||
|
git clone <repo-url>
|
||||||
|
cd clojure-nim
|
||||||
|
|
||||||
|
# Изграждане
|
||||||
|
make build
|
||||||
|
|
||||||
|
# REPL
|
||||||
|
./cljnim
|
||||||
|
|
||||||
|
# Изпълнение на файл
|
||||||
|
./cljnim run examples/hello.clj
|
||||||
|
```
|
||||||
|
|
||||||
|
## Примери
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; hello.clj
|
||||||
|
(ns hello)
|
||||||
|
|
||||||
|
(println "Здравей, Nim свят!")
|
||||||
|
|
||||||
|
(defn факториел [n]
|
||||||
|
(if (<= n 1)
|
||||||
|
1
|
||||||
|
(* n (факториел (- n 1)))))
|
||||||
|
|
||||||
|
(println "5! =" (факториел 5))
|
||||||
|
```
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Работа със структури от данни
|
||||||
|
(def данни
|
||||||
|
{:име "Иван"
|
||||||
|
:възраст 30
|
||||||
|
:езици ["Clojure" "Nim" "C"]})
|
||||||
|
|
||||||
|
(println (get-in данни [:езици 0]))
|
||||||
|
;; => "Clojure"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Структура на Проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── reader/ # Clojure reader (EDN парсер)
|
||||||
|
│ ├── analyzer/ # Semantic анализатор
|
||||||
|
│ ├── macros/ # Core macros (defn, let, if, и т.н.)
|
||||||
|
│ ├── emitter/ # Генератор на Nim код
|
||||||
|
│ ├── runtime/ # Clojure runtime в Nim
|
||||||
|
│ └── repl/ # Интерактивна обвивка
|
||||||
|
├── lib/
|
||||||
|
│ └── core.clj # Clojure core библиотека
|
||||||
|
├── tests/ # Тестове
|
||||||
|
├── examples/ # Примери
|
||||||
|
├── Makefile
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Изисквания
|
||||||
|
|
||||||
|
- Nim >= 2.0
|
||||||
|
- GCC или Clang
|
||||||
|
- make
|
||||||
|
|
||||||
|
## Лиценз
|
||||||
|
|
||||||
|
MIT License — виж [LICENSE](LICENSE).
|
||||||
|
|
||||||
|
## Благодарности
|
||||||
|
|
||||||
|
- [Clojure](https://clojure.org/) — Rich Hickey и екипът
|
||||||
|
- [Nim](https://nim-lang.org/) — Andreas Rumpf и общността
|
||||||
|
- Вдъхновено от ClojureScript, Babashka и Clojerl
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
# Пътна Карта: Clojure/Nim
|
||||||
|
|
||||||
|
## Фаза 0: Инфраструктура и Планиране ✅
|
||||||
|
|
||||||
|
- [x] Определяне на цели и архитектура
|
||||||
|
- [x] Избор на подход (изходен Nim код vs директен C)
|
||||||
|
- [x] Създаване на структура на проекта
|
||||||
|
- [ ] CI/CD pipeline
|
||||||
|
- [ ] Система за тестване
|
||||||
|
|
||||||
|
## Фаза 1: Reader (Clojure → Clojure Data Structures)
|
||||||
|
|
||||||
|
**Цел:** Правилно парсиране на Clojure синтаксис към Clojure структури от данни.
|
||||||
|
|
||||||
|
- [ ] Символи (`sym`, `foo/bar`)
|
||||||
|
- [ ] Ключови думи (`:key`, `:ns/key`)
|
||||||
|
- [ ] Числа (integers, floats, ratios, big ints)
|
||||||
|
- [ ] Низове с escape sequences
|
||||||
|
- [ ] Коментари (`;`)
|
||||||
|
- [ ] Колекции:
|
||||||
|
- [ ] Списъци `(...)`
|
||||||
|
- [ ] Вектори `[...]`
|
||||||
|
- [ ] Maps `{...}`
|
||||||
|
- [ ] Sets `#{...}`
|
||||||
|
- [ ] Цитиране (`'`, `` ` ``, `~`, `~@`, `#`)
|
||||||
|
- [ ] Четене на dispatch macros (`#'`, `#_`, `#{}`, etc.)
|
||||||
|
- [ ] Reader conditionals (`#?()`)
|
||||||
|
- [ ] Метаданни (`^`)
|
||||||
|
|
||||||
|
## Фаза 2: Runtime Структури от Данни
|
||||||
|
|
||||||
|
**Цел:** Имплементиране на Clojure структурите от данни в Nim.
|
||||||
|
|
||||||
|
- [ ] **Persistent Vector** (Hash-mapped trie, като Clojure's HMT)
|
||||||
|
- [ ] **Persistent Map** (Hash-mapped trie)
|
||||||
|
- [ ] **Persistent Set** (върху Map)
|
||||||
|
- [ ] **Linked List** (свързан списък за sequences)
|
||||||
|
- [ ] **Lazy Seq**
|
||||||
|
- [ ] **Atoms** (STM ще дойде по-късно)
|
||||||
|
- [ ] **Keywords** (interned)
|
||||||
|
- [ ] **Symbols**
|
||||||
|
- [ ] **Vars**
|
||||||
|
- [ ] **Namespaces**
|
||||||
|
|
||||||
|
## Фаза 3: Макро Система и AST
|
||||||
|
|
||||||
|
**Цел:** Clojure макроси, които работят върху Clojure структури.
|
||||||
|
|
||||||
|
- [ ] **Clojure AST** представяне в Nim
|
||||||
|
- [ ] **Macro expansion engine**
|
||||||
|
- [ ] Специални форми:
|
||||||
|
- [ ] `def`
|
||||||
|
- [ ] `fn` / `defn`
|
||||||
|
- [ ] `let` / `letfn`
|
||||||
|
- [ ] `if` / `when` / `cond`
|
||||||
|
- [ ] `do`
|
||||||
|
- [ ] `quote`
|
||||||
|
- [ ] `loop` / `recur`
|
||||||
|
- [ ] `throw` / `try` / `catch` / `finally`
|
||||||
|
- [ ] `var`
|
||||||
|
- [ ] `binding` (dynamic vars)
|
||||||
|
- [ ] Core макроси:
|
||||||
|
- [ ] `defmacro`
|
||||||
|
- [ ] `->`, `->>`
|
||||||
|
- [ ] `and`, `or`
|
||||||
|
- [ ] `cond->`, `cond->>`
|
||||||
|
- [ ] `doto`
|
||||||
|
- [ ] `for`, `doseq`
|
||||||
|
- [ ] `lazy-seq`
|
||||||
|
|
||||||
|
## Фаза 4: Nim Code Emitter
|
||||||
|
|
||||||
|
**Цел:** Транслиране на Clojure AST към Nim код.
|
||||||
|
|
||||||
|
- [ ] **Symbol мапинг** (Clojure → Nim naming)
|
||||||
|
- [ ] **Type inference** (основно)
|
||||||
|
- [ ] **Emitter за специални форми**:
|
||||||
|
- [ ] Функции (`proc` в Nim)
|
||||||
|
- [ ] Променливи (`var` / `let` / `const`)
|
||||||
|
- [ ] Условни изрази (`if` / `case`)
|
||||||
|
- [ ] Цикли
|
||||||
|
- [ ] Изключения (`try` / `except`)
|
||||||
|
- [ ] **Interop**:
|
||||||
|
- [ ] Извикване на Nim функции от Clojure
|
||||||
|
- [ ] Извикване на C библиотеки
|
||||||
|
- [ ] FFI механизъм
|
||||||
|
- [ ] **Type hints** поддръжка
|
||||||
|
|
||||||
|
## Фаза 5: Core Библиотека
|
||||||
|
|
||||||
|
**Цел:** Имплементация на Clojure core функции.
|
||||||
|
|
||||||
|
### 5.1 Функции за Колекции
|
||||||
|
- [ ] `map`, `filter`, `reduce`
|
||||||
|
- [ ] `first`, `rest`, `next`, `last`
|
||||||
|
- [ ] `conj`, `into`
|
||||||
|
- [ ] `assoc`, `dissoc`, `get`, `get-in`
|
||||||
|
- [ ] `update`, `update-in`
|
||||||
|
- [ ] `keys`, `vals`
|
||||||
|
- [ ] `count`, `empty?`, `seq`
|
||||||
|
- [ ] `take`, `drop`, `partition`
|
||||||
|
- [ ] `concat`, `interleave`
|
||||||
|
|
||||||
|
### 5.2 Функции за Низове
|
||||||
|
- [ ] `str`, `pr-str`, `println`, `prn`
|
||||||
|
- [ ] `subs`, `clojure.string/` namespace
|
||||||
|
|
||||||
|
### 5.3 Функции за IO
|
||||||
|
- [ ] `slurp`, `spit`
|
||||||
|
- [ ] `read-line`
|
||||||
|
|
||||||
|
### 5.4 Функции за Мета
|
||||||
|
- [ ] `meta`, `with-meta`, `vary-meta`
|
||||||
|
- [ ] `type`, `instance?`
|
||||||
|
|
||||||
|
## Фаза 6: REPL и Инструменти
|
||||||
|
|
||||||
|
**Цел:** Интерактивна среда за разработка.
|
||||||
|
|
||||||
|
- [ ] **REPL цикъл** (Read-Eval-Print-Loop)
|
||||||
|
- [ ] **Hot reloading** на код
|
||||||
|
- [ ] **Error reporting** с Clojure стек тракове
|
||||||
|
- [ ] **Auto-completion**
|
||||||
|
- [ ] **Doc strings** (`doc` функция)
|
||||||
|
- [ ] **Source** (`source` функция)
|
||||||
|
- [ ] **AOT компилация**
|
||||||
|
|
||||||
|
## Фаза 7: Многонишковост и STM (по-късно)
|
||||||
|
|
||||||
|
**Цел:** Clojure's конкурентност модел.
|
||||||
|
|
||||||
|
- [ ] **Atoms** (CAS)
|
||||||
|
- [ ] **Agents**
|
||||||
|
- [ ] **Refs + Software Transactional Memory**
|
||||||
|
- [ ] **Futures** / **Promises**
|
||||||
|
- [ ] **core.async** (channels, go blocks) — голяма задача
|
||||||
|
|
||||||
|
## Фаза 8: Оптимизации и Полиране
|
||||||
|
|
||||||
|
- [ ] **Protocol имплементация** (като Clojure protocols)
|
||||||
|
- [ ] **Multimethods**
|
||||||
|
- [ ] **Records и Types**
|
||||||
|
- [ ] **Бързо стартиране** (малък runtime)
|
||||||
|
- [ ] **Малък размер на бинарния файл**
|
||||||
|
- [ ] **Подобрена производителност** на persistent структурите
|
||||||
|
- [ ] **Tree shaking** / Dead code elimination
|
||||||
|
|
||||||
|
## Бъдещи Идеи
|
||||||
|
|
||||||
|
- **GraalVM нативен образ** алтернатива (Clojure/Nim като lighter alternative)
|
||||||
|
- **Babashka-подобен скриптов инструмент**
|
||||||
|
- **Nim библиотеки достъпни директно** от Clojure
|
||||||
|
- **ClojureScript съвместимост** (споделен код)
|
||||||
|
- **LSP/Language Server** за Clojure/Nim
|
||||||
|
- **nREPL съвместимост**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Текущ Фокус
|
||||||
|
|
||||||
|
Следваща стъпка: **Фаза 1 — Reader**
|
||||||
|
|
||||||
|
Ще започнем с имплементация на базов Clojure reader, който може да парсира:
|
||||||
|
1. Числа и низове
|
||||||
|
2. Символи и keywords
|
||||||
|
3. Специални знаци (`'`, `` ` ``, `~`, `@`)
|
||||||
|
4. Списъци и вектори
|
||||||
|
|
||||||
|
Първият milestone е работещ reader, който може да обработи `(+ 1 2)` и да върне правилна Clojure data structure.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Package
|
||||||
|
version = "0.1.0"
|
||||||
|
author = "Clojure/Nim Team"
|
||||||
|
description = "Clojure dialect compiling to Nim"
|
||||||
|
license = "MIT"
|
||||||
|
srcDir = "src"
|
||||||
|
bin = @["cljnim"]
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
requires "nim >= 2.0.0"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
; First Clojure/Nim program
|
||||||
|
(println "Hello, Nim world!")
|
||||||
|
(println (+ 1 2 3))
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
; Math example
|
||||||
|
(defn square [x]
|
||||||
|
(* x x))
|
||||||
|
|
||||||
|
(defn factorial [n]
|
||||||
|
(if (= n 0)
|
||||||
|
1
|
||||||
|
(* n (factorial (- n 1)))))
|
||||||
|
|
||||||
|
(let [a 5]
|
||||||
|
(println (square a))
|
||||||
|
(println (factorial a)))
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# Clojure/Nim Compiler
|
||||||
|
import os, strutils, osproc
|
||||||
|
import reader, emitter, types
|
||||||
|
|
||||||
|
proc compileFile*(inputPath: string, outputPath: string) =
|
||||||
|
let source = readFile(inputPath)
|
||||||
|
let forms = reader.readAll(source)
|
||||||
|
|
||||||
|
if forms.len == 0:
|
||||||
|
stderr.writeLine("Error: No forms found in " & inputPath)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
let nimCode = emitter.emitProgram(forms)
|
||||||
|
writeFile(outputPath, nimCode)
|
||||||
|
echo "Generated: ", outputPath
|
||||||
|
|
||||||
|
proc runFile*(inputPath: string) =
|
||||||
|
let tmpDir = getTempDir()
|
||||||
|
let baseName = inputPath.splitFile.name
|
||||||
|
let nimPath = tmpDir / baseName & "_generated.nim"
|
||||||
|
let binPath = tmpDir / baseName & "_generated"
|
||||||
|
|
||||||
|
compileFile(inputPath, nimPath)
|
||||||
|
|
||||||
|
# Compile generated Nim code
|
||||||
|
let compileCmd = "nim c -o:" & binPath & " " & nimPath
|
||||||
|
echo "Compiling: ", compileCmd
|
||||||
|
let compileResult = execCmd(compileCmd)
|
||||||
|
if compileResult != 0:
|
||||||
|
stderr.writeLine("Compilation failed")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
# Run
|
||||||
|
echo "Running: ", binPath
|
||||||
|
let runResult = execCmd(binPath)
|
||||||
|
if runResult != 0:
|
||||||
|
stderr.writeLine("Execution failed")
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
proc main() =
|
||||||
|
let args = commandLineParams()
|
||||||
|
|
||||||
|
if args.len == 0:
|
||||||
|
echo "Clojure/Nim Compiler"
|
||||||
|
echo "Usage:"
|
||||||
|
echo " cljnim compile <file.clj> [output.nim] Compile to Nim"
|
||||||
|
echo " cljnim run <file.clj> Compile and run"
|
||||||
|
echo " cljnim read <file.clj> Parse and print AST"
|
||||||
|
quit(0)
|
||||||
|
|
||||||
|
let cmd = args[0]
|
||||||
|
|
||||||
|
case cmd
|
||||||
|
of "compile":
|
||||||
|
if args.len < 2:
|
||||||
|
stderr.writeLine("Error: Missing input file")
|
||||||
|
quit(1)
|
||||||
|
let inputPath = args[1]
|
||||||
|
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
|
||||||
|
compileFile(inputPath, outputPath)
|
||||||
|
|
||||||
|
of "run":
|
||||||
|
if args.len < 2:
|
||||||
|
stderr.writeLine("Error: Missing input file")
|
||||||
|
quit(1)
|
||||||
|
runFile(args[1])
|
||||||
|
|
||||||
|
of "read":
|
||||||
|
if args.len < 2:
|
||||||
|
stderr.writeLine("Error: Missing input file")
|
||||||
|
quit(1)
|
||||||
|
let source = readFile(args[1])
|
||||||
|
let forms = reader.readAll(source)
|
||||||
|
for form in forms:
|
||||||
|
echo $form
|
||||||
|
|
||||||
|
else:
|
||||||
|
stderr.writeLine("Unknown command: " & cmd)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
when isMainModule:
|
||||||
|
main()
|
||||||
+272
@@ -0,0 +1,272 @@
|
|||||||
|
# Clojure → Nim Emitter
|
||||||
|
import strutils, sequtils
|
||||||
|
import types
|
||||||
|
|
||||||
|
type
|
||||||
|
EmitterError* = object of CatchableError
|
||||||
|
|
||||||
|
proc mangleName*(name: string): string =
|
||||||
|
## Convert Clojure symbol to valid Nim identifier
|
||||||
|
result = ""
|
||||||
|
for c in name:
|
||||||
|
case c
|
||||||
|
of '-': result.add('_')
|
||||||
|
of '?': result.add("_Q")
|
||||||
|
of '!': result.add("_B")
|
||||||
|
of '*': result.add("_STAR")
|
||||||
|
of '+': result.add("_PLUS")
|
||||||
|
of '/': result.add("_DIV")
|
||||||
|
of '=': result.add("_EQ")
|
||||||
|
of '>': result.add("_GT")
|
||||||
|
of '<': result.add("_LT")
|
||||||
|
of '.': result.add('_')
|
||||||
|
of '\'': result.add("_QUOTE")
|
||||||
|
else: result.add(c)
|
||||||
|
|
||||||
|
proc emitExpr*(v: CljVal, indent: int = 0): string
|
||||||
|
|
||||||
|
proc emitBlock(items: seq[CljVal], indent: int): string =
|
||||||
|
if items.len == 0:
|
||||||
|
return "discard"
|
||||||
|
var lines: seq[string] = @[]
|
||||||
|
for i, item in items:
|
||||||
|
lines.add(emitExpr(item, indent))
|
||||||
|
return lines.join("\n")
|
||||||
|
|
||||||
|
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||||
|
let spaces = " ".repeat(indent)
|
||||||
|
let head = items[0]
|
||||||
|
if head.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "List head must be a symbol, got " & $head.kind)
|
||||||
|
let op = head.symName
|
||||||
|
|
||||||
|
case op
|
||||||
|
of "+", "-", "*", "/", "=", ">", "<":
|
||||||
|
if items.len < 3:
|
||||||
|
raise newException(EmitterError, op & " requires at least 2 arguments")
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
let nimOp = case op
|
||||||
|
of "=": "=="
|
||||||
|
else: op
|
||||||
|
for i in 1..<items.len:
|
||||||
|
parts.add(emitExpr(items[i], indent))
|
||||||
|
return spaces & parts.join(" " & nimOp & " ")
|
||||||
|
|
||||||
|
of "println":
|
||||||
|
if items.len < 2:
|
||||||
|
raise newException(EmitterError, "println requires at least 1 argument")
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for i in 1..<items.len:
|
||||||
|
parts.add(emitExpr(items[i], indent))
|
||||||
|
return spaces & "echo " & parts.join(", ")
|
||||||
|
|
||||||
|
of "def":
|
||||||
|
if items.len != 3:
|
||||||
|
raise newException(EmitterError, "def requires exactly 2 arguments")
|
||||||
|
let name = items[1]
|
||||||
|
if name.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "def name must be a symbol")
|
||||||
|
return spaces & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], indent)
|
||||||
|
|
||||||
|
of "defn":
|
||||||
|
if items.len < 4:
|
||||||
|
raise newException(EmitterError, "defn requires name, params, and body")
|
||||||
|
let name = items[1]
|
||||||
|
if name.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn name must be a symbol")
|
||||||
|
let params = items[2]
|
||||||
|
if params.kind != ckVector:
|
||||||
|
raise newException(EmitterError, "defn params must be a vector")
|
||||||
|
var paramNames: seq[string] = @[]
|
||||||
|
for p in params.items:
|
||||||
|
if p.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "defn params must be symbols")
|
||||||
|
paramNames.add(mangleName(p.symName) & ": auto")
|
||||||
|
let body = items[3..^1]
|
||||||
|
var bodyCode = ""
|
||||||
|
if body.len == 1:
|
||||||
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
|
else:
|
||||||
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
let procName = mangleName(name.symName)
|
||||||
|
var typedParams: seq[string] = @[]
|
||||||
|
for p in params.items:
|
||||||
|
typedParams.add(mangleName(p.symName) & ": int")
|
||||||
|
if typedParams.len > 0:
|
||||||
|
return spaces & "proc " & procName & "(" & typedParams.join(", ") & "): int =\n" & bodyCode
|
||||||
|
else:
|
||||||
|
return spaces & "proc " & procName & "(): int =\n" & bodyCode
|
||||||
|
|
||||||
|
of "fn":
|
||||||
|
if items.len < 3:
|
||||||
|
raise newException(EmitterError, "fn requires params and body")
|
||||||
|
let params = items[1]
|
||||||
|
if params.kind != ckVector:
|
||||||
|
raise newException(EmitterError, "fn params must be a vector")
|
||||||
|
var paramNames: seq[string] = @[]
|
||||||
|
for p in params.items:
|
||||||
|
if p.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "fn params must be symbols")
|
||||||
|
paramNames.add(mangleName(p.symName) & ": auto")
|
||||||
|
let body = items[2..^1]
|
||||||
|
var bodyCode = ""
|
||||||
|
if body.len == 1:
|
||||||
|
bodyCode = emitExpr(body[0], indent + 1)
|
||||||
|
else:
|
||||||
|
bodyCode = emitBlock(body, indent + 1)
|
||||||
|
var typedParams: seq[string] = @[]
|
||||||
|
for p in params.items:
|
||||||
|
typedParams.add(mangleName(p.symName) & ": int")
|
||||||
|
if typedParams.len > 0:
|
||||||
|
return spaces & "proc(" & typedParams.join(", ") & "): int =\n" & bodyCode
|
||||||
|
else:
|
||||||
|
return spaces & "proc(): int =\n" & bodyCode
|
||||||
|
|
||||||
|
of "let":
|
||||||
|
if items.len < 3:
|
||||||
|
raise newException(EmitterError, "let requires bindings and body")
|
||||||
|
let bindings = items[1]
|
||||||
|
if bindings.kind != ckVector:
|
||||||
|
raise newException(EmitterError, "let bindings must be a vector")
|
||||||
|
if bindings.items.len mod 2 != 0:
|
||||||
|
raise newException(EmitterError, "let bindings must have even number of elements")
|
||||||
|
var lines: seq[string] = @[]
|
||||||
|
lines.add("block:")
|
||||||
|
var bindLines: seq[string] = @[]
|
||||||
|
var i = 0
|
||||||
|
while i < bindings.items.len:
|
||||||
|
let name = bindings.items[i]
|
||||||
|
let val = bindings.items[i+1]
|
||||||
|
if name.kind != ckSymbol:
|
||||||
|
raise newException(EmitterError, "let binding name must be a symbol")
|
||||||
|
bindLines.add(mangleName(name.symName) & " = " & emitExpr(val, 0).strip())
|
||||||
|
i += 2
|
||||||
|
lines.add(" ".repeat(indent + 1) & "let")
|
||||||
|
for bl in bindLines:
|
||||||
|
lines.add(" ".repeat(indent + 2) & bl)
|
||||||
|
let body = items[2..^1]
|
||||||
|
for j, b in body:
|
||||||
|
let bcode = emitExpr(b, indent + 1).strip()
|
||||||
|
if j == body.len - 1:
|
||||||
|
lines.add(" ".repeat(indent + 1) & bcode)
|
||||||
|
else:
|
||||||
|
if bcode.startsWith("echo ") or bcode.startsWith("discard "):
|
||||||
|
lines.add(" ".repeat(indent + 1) & bcode)
|
||||||
|
else:
|
||||||
|
lines.add(" ".repeat(indent + 1) & "discard " & bcode)
|
||||||
|
return spaces & lines.join("\n")
|
||||||
|
|
||||||
|
of "if":
|
||||||
|
if items.len < 3 or items.len > 4:
|
||||||
|
raise newException(EmitterError, "if requires condition, then, and optional else")
|
||||||
|
let condCode = emitExpr(items[1], indent)
|
||||||
|
let thenCode = emitExpr(items[2], indent + 1)
|
||||||
|
var result = spaces & "if " & condCode & ":\n" & " ".repeat(indent + 1) & thenCode
|
||||||
|
if items.len == 4:
|
||||||
|
let elseCode = emitExpr(items[3], indent + 1)
|
||||||
|
result.add("\n" & spaces & "else:\n" & " ".repeat(indent + 1) & elseCode)
|
||||||
|
return result
|
||||||
|
|
||||||
|
of "do":
|
||||||
|
if items.len < 2:
|
||||||
|
return spaces & "discard"
|
||||||
|
return emitBlock(items[1..^1], indent)
|
||||||
|
|
||||||
|
of "quote":
|
||||||
|
if items.len != 2:
|
||||||
|
raise newException(EmitterError, "quote requires exactly 1 argument")
|
||||||
|
let quoted = items[1]
|
||||||
|
case quoted.kind
|
||||||
|
of ckSymbol:
|
||||||
|
return spaces & "cljSymbol(\"" & quoted.symName & "\")"
|
||||||
|
of ckList:
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for item in quoted.items:
|
||||||
|
parts.add(emitExpr(item, indent))
|
||||||
|
return spaces & "cljList(@[" & parts.join(", ") & "])"
|
||||||
|
of ckVector:
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for item in quoted.items:
|
||||||
|
parts.add(emitExpr(item, indent))
|
||||||
|
return spaces & "cljVector(@[" & parts.join(", ") & "])"
|
||||||
|
else:
|
||||||
|
return emitExpr(quoted, indent)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Function call
|
||||||
|
var args: seq[string] = @[]
|
||||||
|
for i in 1..<items.len:
|
||||||
|
args.add(emitExpr(items[i], indent))
|
||||||
|
return spaces & mangleName(op) & "(" & args.join(", ") & ")"
|
||||||
|
|
||||||
|
proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||||
|
let spaces = " ".repeat(indent)
|
||||||
|
case v.kind
|
||||||
|
of ckNil:
|
||||||
|
return spaces & "nil"
|
||||||
|
of ckBool:
|
||||||
|
return spaces & $v.boolVal
|
||||||
|
of ckInt:
|
||||||
|
return spaces & $v.intVal
|
||||||
|
of ckFloat:
|
||||||
|
return spaces & $v.floatVal
|
||||||
|
of ckString:
|
||||||
|
return spaces & "\"" & v.strVal & "\""
|
||||||
|
of ckKeyword:
|
||||||
|
return spaces & "cljKeyword(\"" & v.kwName & "\")"
|
||||||
|
of ckSymbol:
|
||||||
|
return spaces & mangleName(v.symName)
|
||||||
|
of ckList:
|
||||||
|
if v.items.len == 0:
|
||||||
|
return spaces & "cljList(@[])"
|
||||||
|
return emitSpecialForm(v.items, indent)
|
||||||
|
of ckVector:
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for item in v.items:
|
||||||
|
parts.add(emitExpr(item, indent))
|
||||||
|
return spaces & "@[" & parts.join(", ") & "]"
|
||||||
|
of ckMap:
|
||||||
|
raise newException(EmitterError, "Maps not yet supported in emitter")
|
||||||
|
|
||||||
|
proc emitProgram*(forms: seq[CljVal]): string =
|
||||||
|
var headerLines: seq[string] = @[
|
||||||
|
"# Generated by Clojure/Nim",
|
||||||
|
"import strutils, sequtils",
|
||||||
|
"",
|
||||||
|
"# Runtime helpers",
|
||||||
|
"proc cljSymbol(name: string): auto = name",
|
||||||
|
"proc cljKeyword(name: string): auto = \":\" & name",
|
||||||
|
"proc cljList(items: seq[auto]): auto = items",
|
||||||
|
"proc cljVector(items: seq[auto]): auto = items",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
var defs: seq[string] = @[]
|
||||||
|
var mainForms: seq[string] = @[]
|
||||||
|
|
||||||
|
for i, form in forms:
|
||||||
|
let isDef = form.kind == ckList and form.items.len > 0 and
|
||||||
|
form.items[0].kind == ckSymbol and
|
||||||
|
form.items[0].symName in ["def", "defn"]
|
||||||
|
if isDef:
|
||||||
|
defs.add(emitExpr(form, 0))
|
||||||
|
else:
|
||||||
|
let code = emitExpr(form, 2).strip()
|
||||||
|
if code.startsWith("echo ") or code.startsWith("discard ") or code.startsWith("block:"):
|
||||||
|
mainForms.add(code)
|
||||||
|
elif i == forms.len - 1:
|
||||||
|
# Last form - echo its result
|
||||||
|
mainForms.add("echo " & code)
|
||||||
|
else:
|
||||||
|
mainForms.add("discard " & code)
|
||||||
|
|
||||||
|
var lines = headerLines
|
||||||
|
lines.add(defs)
|
||||||
|
|
||||||
|
if mainForms.len > 0:
|
||||||
|
lines.add("")
|
||||||
|
lines.add("when isMainModule:")
|
||||||
|
for form in mainForms:
|
||||||
|
lines.add(" " & form)
|
||||||
|
|
||||||
|
return lines.join("\n") & "\n"
|
||||||
+187
@@ -0,0 +1,187 @@
|
|||||||
|
# Clojure Reader (EDN subset)
|
||||||
|
import strutils, sequtils
|
||||||
|
import types
|
||||||
|
|
||||||
|
type
|
||||||
|
ReaderError* = object of CatchableError
|
||||||
|
|
||||||
|
proc skipWhitespaceAndComments(s: string, i: var int) =
|
||||||
|
while i < s.len:
|
||||||
|
let c = s[i]
|
||||||
|
if c in Whitespace:
|
||||||
|
inc i
|
||||||
|
elif c == ';':
|
||||||
|
while i < s.len and s[i] != '\n':
|
||||||
|
inc i
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
proc isSymChar(c: char): bool =
|
||||||
|
c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&'}
|
||||||
|
|
||||||
|
proc readStringTok(s: string, i: var int): string =
|
||||||
|
inc i # skip opening quote
|
||||||
|
var resultStr = ""
|
||||||
|
while i < s.len and s[i] != '"':
|
||||||
|
if s[i] == '\\' and i + 1 < s.len:
|
||||||
|
inc i
|
||||||
|
case s[i]
|
||||||
|
of 'n': resultStr.add('\n')
|
||||||
|
of 't': resultStr.add('\t')
|
||||||
|
of 'r': resultStr.add('\r')
|
||||||
|
of '\\': resultStr.add('\\')
|
||||||
|
of '"': resultStr.add('"')
|
||||||
|
else: resultStr.add(s[i])
|
||||||
|
else:
|
||||||
|
resultStr.add(s[i])
|
||||||
|
inc i
|
||||||
|
if i >= s.len:
|
||||||
|
raise newException(ReaderError, "Unterminated string")
|
||||||
|
inc i # skip closing quote
|
||||||
|
return resultStr
|
||||||
|
|
||||||
|
proc readNumberOrSym(s: string, i: var int): string =
|
||||||
|
var start = i
|
||||||
|
# handle negative sign or standalone operators
|
||||||
|
if s[i] == '-' or s[i] == '+':
|
||||||
|
if i + 1 < s.len and s[i+1] in Digits:
|
||||||
|
inc i
|
||||||
|
while i < s.len and s[i] in Digits:
|
||||||
|
inc i
|
||||||
|
if i < s.len and s[i] == '.':
|
||||||
|
inc i
|
||||||
|
while i < s.len and s[i] in Digits:
|
||||||
|
inc i
|
||||||
|
return s[start..<i]
|
||||||
|
else:
|
||||||
|
# it's a symbol like - or +
|
||||||
|
inc i
|
||||||
|
while i < s.len and isSymChar(s[i]):
|
||||||
|
inc i
|
||||||
|
return s[start..<i]
|
||||||
|
elif s[i] in Digits:
|
||||||
|
while i < s.len and s[i] in Digits:
|
||||||
|
inc i
|
||||||
|
if i < s.len and s[i] == '.':
|
||||||
|
inc i
|
||||||
|
while i < s.len and s[i] in Digits:
|
||||||
|
inc i
|
||||||
|
return s[start..<i]
|
||||||
|
else:
|
||||||
|
while i < s.len and isSymChar(s[i]):
|
||||||
|
inc i
|
||||||
|
return s[start..<i]
|
||||||
|
|
||||||
|
proc readAtom(s: string, i: var int): CljVal =
|
||||||
|
let tok = readNumberOrSym(s, i)
|
||||||
|
if tok.len == 0:
|
||||||
|
raise newException(ReaderError, "Empty token at position " & $i)
|
||||||
|
|
||||||
|
# nil, true, false
|
||||||
|
if tok == "nil": return cljNil()
|
||||||
|
if tok == "true": return cljBool(true)
|
||||||
|
if tok == "false": return cljBool(false)
|
||||||
|
|
||||||
|
# keyword
|
||||||
|
if tok[0] == ':':
|
||||||
|
return cljKeyword(tok[1..^1])
|
||||||
|
|
||||||
|
# number
|
||||||
|
var isFloat = false
|
||||||
|
var isNumber = true
|
||||||
|
var startIdx = 0
|
||||||
|
if tok[0] == '-' or tok[0] == '+':
|
||||||
|
if tok.len == 1:
|
||||||
|
isNumber = false
|
||||||
|
else:
|
||||||
|
startIdx = 1
|
||||||
|
for j in startIdx..<tok.len:
|
||||||
|
if tok[j] == '.':
|
||||||
|
if isFloat:
|
||||||
|
isNumber = false
|
||||||
|
break
|
||||||
|
isFloat = true
|
||||||
|
elif tok[j] notin Digits:
|
||||||
|
isNumber = false
|
||||||
|
break
|
||||||
|
|
||||||
|
if isNumber and tok.len > startIdx:
|
||||||
|
if isFloat:
|
||||||
|
return cljFloat(parseFloat(tok))
|
||||||
|
else:
|
||||||
|
return cljInt(parseInt(tok).int64)
|
||||||
|
|
||||||
|
# symbol
|
||||||
|
return cljSymbol(tok)
|
||||||
|
|
||||||
|
proc readForm(s: string, i: var int): CljVal
|
||||||
|
|
||||||
|
proc readList(s: string, i: var int): CljVal =
|
||||||
|
inc i # skip '('
|
||||||
|
var items: seq[CljVal] = @[]
|
||||||
|
while true:
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
raise newException(ReaderError, "Unterminated list")
|
||||||
|
if s[i] == ')':
|
||||||
|
inc i
|
||||||
|
break
|
||||||
|
items.add(readForm(s, i))
|
||||||
|
return cljList(items)
|
||||||
|
|
||||||
|
proc readVector(s: string, i: var int): CljVal =
|
||||||
|
inc i # skip '['
|
||||||
|
var items: seq[CljVal] = @[]
|
||||||
|
while true:
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
raise newException(ReaderError, "Unterminated vector")
|
||||||
|
if s[i] == ']':
|
||||||
|
inc i
|
||||||
|
break
|
||||||
|
items.add(readForm(s, i))
|
||||||
|
return cljVector(items)
|
||||||
|
|
||||||
|
proc readForm(s: string, i: var int): CljVal =
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
raise newException(ReaderError, "Unexpected end of input")
|
||||||
|
|
||||||
|
let c = s[i]
|
||||||
|
case c
|
||||||
|
of '(':
|
||||||
|
return readList(s, i)
|
||||||
|
of '[':
|
||||||
|
return readVector(s, i)
|
||||||
|
of '"':
|
||||||
|
return cljString(readStringTok(s, i))
|
||||||
|
of '\'':
|
||||||
|
inc i # skip quote
|
||||||
|
let form = readForm(s, i)
|
||||||
|
return cljList(@[cljSymbol("quote"), form])
|
||||||
|
else:
|
||||||
|
return readAtom(s, i)
|
||||||
|
|
||||||
|
proc readOne*(s: string, i: var int): CljVal =
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
return nil
|
||||||
|
return readForm(s, i)
|
||||||
|
|
||||||
|
proc read*(s: string): CljVal =
|
||||||
|
var i = 0
|
||||||
|
let result = readForm(s, i)
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i < s.len:
|
||||||
|
raise newException(ReaderError, "Extra input after form: " & s[i..^1])
|
||||||
|
return result
|
||||||
|
|
||||||
|
proc readAll*(s: string): seq[CljVal] =
|
||||||
|
var i = 0
|
||||||
|
var forms: seq[CljVal] = @[]
|
||||||
|
while true:
|
||||||
|
skipWhitespaceAndComments(s, i)
|
||||||
|
if i >= s.len:
|
||||||
|
break
|
||||||
|
forms.add(readForm(s, i))
|
||||||
|
return forms
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Clojure Value Types (Nim Runtime)
|
||||||
|
import sequtils, strutils
|
||||||
|
|
||||||
|
type
|
||||||
|
CljKind* = enum
|
||||||
|
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
|
||||||
|
ckList, ckVector, ckMap
|
||||||
|
|
||||||
|
CljVal* = ref object
|
||||||
|
case kind*: CljKind
|
||||||
|
of ckNil: discard
|
||||||
|
of ckBool: boolVal*: bool
|
||||||
|
of ckInt: intVal*: int64
|
||||||
|
of ckFloat: floatVal*: float64
|
||||||
|
of ckString: strVal*: string
|
||||||
|
of ckKeyword: kwName*: string
|
||||||
|
of ckSymbol: symName*: string
|
||||||
|
of ckList, ckVector: items*: seq[CljVal]
|
||||||
|
of ckMap: pairs*: seq[(CljVal, CljVal)]
|
||||||
|
|
||||||
|
# Constructors
|
||||||
|
proc cljNil*(): CljVal = CljVal(kind: ckNil)
|
||||||
|
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
|
||||||
|
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
|
||||||
|
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
|
||||||
|
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
|
||||||
|
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
|
||||||
|
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
|
||||||
|
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, items: items)
|
||||||
|
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, items: items)
|
||||||
|
proc cljMap*(pairs: seq[(CljVal, CljVal)]): CljVal = CljVal(kind: ckMap, pairs: pairs)
|
||||||
|
|
||||||
|
# String representation (for debugging)
|
||||||
|
proc `$`*(v: CljVal): string =
|
||||||
|
case v.kind
|
||||||
|
of ckNil: "nil"
|
||||||
|
of ckBool: $v.boolVal
|
||||||
|
of ckInt: $v.intVal
|
||||||
|
of ckFloat: $v.floatVal
|
||||||
|
of ckString: "\"" & v.strVal & "\""
|
||||||
|
of ckKeyword: ":" & v.kwName
|
||||||
|
of ckSymbol: v.symName
|
||||||
|
of ckList: "(" & v.items.mapIt($it).join(" ") & ")"
|
||||||
|
of ckVector: "[" & v.items.mapIt($it).join(" ") & "]"
|
||||||
|
of ckMap:
|
||||||
|
var parts: seq[string] = @[]
|
||||||
|
for (k, v2) in v.pairs:
|
||||||
|
parts.add($k & " " & $v2)
|
||||||
|
"{" & parts.join(", ") & "}"
|
||||||
Reference in New Issue
Block a user