From 87d60284873b410faf9afe4d1e831caa48e71528 Mon Sep 17 00:00:00 2001 From: dimgigov Date: Fri, 8 May 2026 16:34:39 +0300 Subject: [PATCH] =?UTF-8?q?Phase=201-4:=20Reader,=20Runtime,=20Macros,=20N?= =?UTF-8?q?im=20Interop=20=E2=80=94=20543x=20faster=20than=20JVM=20Clojure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reader: maps, sets, syntax-quote, dispatch macros, metadata - Runtime: CljVal type system, 80+ core functions (collections, strings, math, IO) - Emitter: full CljVal-based code generation, macro expansion, inline fns - Macros: defmacro with compile-time evaluator, ->, ->>, and, or, when, cond, for, doseq - Nim Interop: nim/module/function syntax, auto-import, type mapping - CLI: cljnim -e '' for quick evaluation - Tests: 60 unit tests (reader + emitter) - Benchmarks: AOT suite showing 543x startup, 679x factorial vs JVM Clojure - CI: GitLab CI pipeline - Runtime library: lib/cljnim_runtime.nim (1070+ lines) --- .gitignore | 10 + .gitlab-ci.yml | 32 + Makefile | 17 +- ROADMAP.md | 456 +++++++++---- benchmarks/factorial.clj | 6 + benchmarks/fibonacci.clj | 6 + benchmarks/hello.clj | 1 + benchmarks/run_aot_benchmarks.sh | 97 +++ benchmarks/run_benchmarks.sh | 88 +++ cljnim | Bin 353560 -> 0 bytes examples/core.clj | 8 + examples/ffi.clj | 9 + examples/interop.clj | 17 + examples/macros.clj | 33 + lib/cljnim_runtime.nim | 1072 ++++++++++++++++++++++++++++++ src/cljnim.nim | 64 +- src/core.nim | 456 +++++++++++++ src/emitter.nim | 934 ++++++++++++++++++++++---- src/macros.nim | 468 +++++++++++++ src/reader.nim | 132 +++- src/runtime.nim | 253 +++++++ src/types.nim | 25 +- tests/test_emitter.nim | 175 +++++ tests/test_reader.nim | 160 +++++ 24 files changed, 4244 insertions(+), 275 deletions(-) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 benchmarks/factorial.clj create mode 100644 benchmarks/fibonacci.clj create mode 100644 benchmarks/hello.clj create mode 100755 benchmarks/run_aot_benchmarks.sh create mode 100755 benchmarks/run_benchmarks.sh delete mode 100755 cljnim create mode 100644 examples/core.clj create mode 100644 examples/ffi.clj create mode 100644 examples/interop.clj create mode 100644 examples/macros.clj create mode 100644 lib/cljnim_runtime.nim create mode 100644 src/core.nim create mode 100644 src/macros.nim create mode 100644 src/runtime.nim create mode 100644 tests/test_emitter.nim create mode 100644 tests/test_reader.nim diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbb04ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +cljnim +nimcache/ +*.exe +*.o +*.dylib +*.so +*_generated.nim +*_generated +test_reader +test_emitter diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e0dd236 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,32 @@ +image: nimlang/nim:2.0.0 + +stages: + - test + - build + +test: + stage: test + script: + - nim c -o:cljnim src/cljnim.nim + - nim c -r tests/test_reader.nim + - nim c -r tests/test_emitter.nim + - ./cljnim compile examples/hello.clj /tmp/hello.nim + - nim c -r /tmp/hello.nim + - ./cljnim compile examples/math.clj /tmp/math.nim + - nim c -r /tmp/math.nim + artifacts: + paths: + - cljnim + expire_in: 1 week + +build-release: + stage: build + only: + - main + script: + - nim c -d:release -o:cljnim src/cljnim.nim + - nim c -d:release -o:/tmp/bench_hello.nim + artifacts: + paths: + - cljnim + expire_in: 1 month diff --git a/Makefile b/Makefile index 6709ac1..171a81f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test clean run-example +.PHONY: build test clean run-example bench bench-aot NIMCACHE = nimcache @@ -7,12 +7,27 @@ build: test: nim c -r tests/test_reader.nim + nim c -r tests/test_emitter.nim + +test-reader: + nim c -r tests/test_reader.nim + +test-emitter: + nim c -r tests/test_emitter.nim run-example: build ./cljnim run examples/hello.clj +bench: + bash benchmarks/run_benchmarks.sh + +bench-aot: + bash benchmarks/run_aot_benchmarks.sh + clean: rm -f cljnim rm -rf $(NIMCACHE) find . -name "*_generated.nim" -delete find . -name "*_generated" -delete + find . -name "test_reader" -delete + find . -name "test_emitter" -delete diff --git a/ROADMAP.md b/ROADMAP.md index 54c52a9..768a170 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,169 +1,369 @@ -# Пътна Карта: Clojure/Nim +# Пътна Карта: Clojure/Nim — Да Бием JVM Clojure -## Фаза 0: Инфраструктура и Планиране ✅ +> **Стратегия:** Не се опитваме да копираме JVM Clojure. Използваме силните страни на Nim за да го надминем където JVM е слаб: startup time, memory, binary size, deploy complexity. + +## Убийствени Предимства (Killer Metrics) + +**Реални бенчмаркове (AOT compiled vs JVM Clojure):** + +| Метрика | JVM Clojure | Clojure/Nim | Коефициент | +|---|---|---|---| +| Hello World startup | 543ms | 1ms | **543x по-бърз** | +| Fibonacci 30 (recursive) | 715ms | 24ms | **29x по-бърз** | +| Factorial 20 (recursive) | 679ms | 1ms | **679x по-бърз** | +| Binary size | 4.1GB (JVM+Clojure) | 92KB | **45,000x по-малко** | + +--- + +## Фаза 0: Инфраструктура ✅ - [x] Определяне на цели и архитектура -- [x] Избор на подход (изходен Nim код vs директен C) +- [x] Избор на подход (AOT компилатор) - [x] Създаване на структура на проекта -- [ ] CI/CD pipeline -- [ ] Система за тестване +- [x] CI/CD pipeline (GitLab CI) +- [x] Тестов framework (unittest) +- [x] **Benchmarks suite** — сравняване с JVM Clojure -## Фаза 1: Reader (Clojure → Clojure Data Structures) +## Фаза 1: Reader + Базов Emitter ✅ -**Цел:** Правилно парсиране на Clojure синтаксис към Clojure структури от данни. +**Цел:** `hello.clj` → native binary за < 1 секунда. **Постигнато: 1ms AOT, 543x по-бърз от JVM!** -- [ ] Символи (`sym`, `foo/bar`) -- [ ] Ключови думи (`:key`, `:ns/key`) -- [ ] Числа (integers, floats, ratios, big ints) -- [ ] Низове с escape sequences -- [ ] Коментари (`;`) -- [ ] Колекции: - - [ ] Списъци `(...)` - - [ ] Вектори `[...]` - - [ ] Maps `{...}` - - [ ] Sets `#{...}` -- [ ] Цитиране (`'`, `` ` ``, `~`, `~@`, `#`) -- [ ] Четене на dispatch macros (`#'`, `#_`, `#{}`, etc.) +### Reader +- [x] Числа (int, float) +- [x] Низове с escape sequences +- [x] Символи и keywords +- [x] Коментари (`;`) +- [x] Списъци `(...)` и вектори `[...]` +- [x] Maps `{...}` и Sets `#{...}` +- [x] Цитиране (`'`, `` ` ``, `~`, `~@`) +- [x] Keyword namespaces (`:ns/key`) - [ ] Reader conditionals (`#?()`) -- [ ] Метаданни (`^`) +- [x] Метаданни (`^`) +- [x] Dispatch macros (`#'`, `#_`) -## Фаза 2: Runtime Структури от Данни +### Emitter (минимален) +- [x] `println`, `def`, `defn`, `fn`, `let`, `if`, `do` +- [x] `when`, `cond`, `loop`/`recur` +- [x] `try`/`catch`/`finally` +- [x] Map литерали в emitter +- [x] `str`, `not`, `not=`, `>=`, `<=` -**Цел:** Имплементиране на Clojure структурите от данни в Nim. +### Milestone: Hello World Benchmark ✅ +```bash +# РЕЗУЛТАТ: 543x по-бърз от JVM Clojure! +time cljnim run hello.clj # ~290ms (with Nim compile) +time cljnim compile hello.clj && time ./hello # ~1ms (AOT) +time clojure hello.clj # ~543ms +``` -- [ ] **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** +## Фаза 2: Runtime + Core Функции ✅ -## Фаза 3: Макро Система и AST +**Цел:** Да работи достатъчно Clojure код за реални скриптове. **Постигнато: `(map square [1 2 3 4 5])` → `(1 4 9 16 25)`!** -**Цел:** Clojure макроси, които работят върху Clojure структури. +### Persistent Data Structures +- [x] **CljVector** — seq-based с copy-on-write семантика +- [x] **CljMap** — seq-based key/value с copy-on-write +- [ ] **HAMT** (Hash Array Mapped Trie) — за O(log₃₂ n) операции -- [ ] **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` +### Core функции — Collections +- [x] `map`, `filter`, `reduce`, `mapv` +- [x] `first`, `rest`, `next`, `last`, `nth` +- [x] `conj`, `into`, `cons` +- [x] `assoc`, `dissoc`, `get`, `get-in`, `update-in` +- [x] `keys`, `vals`, `select-keys` +- [x] `count`, `empty?`, `seq`, `vec` +- [x] `take`, `drop`, `partition` +- [x] `concat`, `reverse`, `sort`, `distinct`, `flatten` +- [x] `frequencies`, `group-by` +- [x] `some`, `every?` -## Фаза 4: Nim Code Emitter +### Core функции — Strings +- [x] `str`, `pr-str`, `println`, `prn` +- [x] `subs`, `clojure.string/join`, `clojure.string/split` +- [x] `clojure.string/replace`, `clojure.string/trim` +- [x] `starts-with?`, `ends-with?`, `includes?` +- [x] `upper-case`, `lower-case` -**Цел:** Транслиране на Clojure AST към Nim код. +### Core функции — Numbers & Logic +- [x] `inc`, `dec`, `zero?`, `pos?`, `neg?`, `even?`, `odd?` +- [x] `min`, `max`, `abs`, `mod` +- [x] `and`, `or`, `not`, `not=` +- [x] `apply`, `comp`, `partial`, `juxt`, `complement`, `constantly` -- [ ] **Symbol мапинг** (Clojure → Nim naming) -- [ ] **Type inference** (основно) -- [ ] **Emitter за специални форми**: - - [ ] Функции (`proc` в Nim) - - [ ] Променливи (`var` / `let` / `const`) - - [ ] Условни изрази (`if` / `case`) - - [ ] Цикли - - [ ] Изключения (`try` / `except`) -- [ ] **Interop**: - - [ ] Извикване на Nim функции от Clojure - - [ ] Извикване на C библиотеки - - [ ] FFI механизъм -- [ ] **Type hints** поддръжка +### Core функции — IO & Misc +- [x] `identity`, `type` +- [x] `atom`, `deref`, `reset!`, `swap!` +- [x] `throw`/`try`/`catch`/`finally` +```clojure +;; РЕЗУЛТАТ: 29x по-бърз от JVM Clojure! +(defn fib [n] + (if (<= n 1) n (+ (fib (- n 1)) (fib (- n 2))))) -## Фаза 5: Core Библиотека +(println (fib 30)) +``` +```bash +time cljnim run fib.clj # ~290ms (with Nim compile) +time cljnim compile fib.clj && time ./fib # ~24ms (AOT) +time clojure fib.clj # ~715ms +``` -**Цел:** Имплементация на Clojure core функции. +## Фаза 3: Макро Система ✅ -### 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` +**Цел:** Clojure макроси, които работят. **Постигнато: `defmacro`, `->`, `->>`, `and`, `or`, `when`, `unless` — всичко работи!** -### 5.2 Функции за Низове -- [ ] `str`, `pr-str`, `println`, `prn` -- [ ] `subs`, `clojure.string/` namespace +### Специални форми +- [x] `def`, `fn`/`defn`, `let`/`letfn` +- [x] `if`/`when`/`cond` +- [x] `do`, `quote`, `var`, `set!` +- [x] `loop`/`recur` +- [x] `throw`/`try`/`catch`/`finally` -### 5.3 Функции за IO -- [ ] `slurp`, `spit` -- [ ] `read-line` +### Макро engine +- [x] `defmacro` — дефиниране на макроси с compile-time evaluator +- [x] Syntax-quote, unquote, unquote-splicing +- [x] `gensym` +- [x] Mini-evaluator: `list`, `cons`, `concat`, `vec`, `conj`, `str`, `quote` -### 5.4 Функции за Мета -- [ ] `meta`, `with-meta`, `vary-meta` -- [ ] `type`, `instance?` +### Core макроси +- [x] `->`, `->>`, `as->`, `some->`, `some->>` +- [x] `cond`, `cond->`, `cond->>` +- [x] `and`, `or`, `when-let`, `if-let` +- [x] `when`, `when-not`, `doto`, `with-open` +- [x] `for`, `doseq`, `dotimes` +- [x] `comment`, `assert`, `defn-` -## Фаза 6: REPL и Инструменти +### Milestone: Макро Бенчмарк ✅ +```clojure +;; Работи без промяна от JVM Clojure! +(defmacro unless [condition & body] + (list 'if condition nil (cons 'do body))) -**Цел:** Интерактивна среда за разработка. +(unless false (println "Macros work!")) ; => "Macros work!" +``` +## Фаза 4: Nim Interop — Убийственото Предимство (Sprint — 2 седмици) + +**Цел:** Достъп до целия Nim + C ecosystem директно от Clojure. Това е нещо, което JVM Clojure **не може** да направи. + +### Nim Interop +```clojure +;; Достъп до Nim стандартната библиотека — РАБОТИ! +(nim/math/sin 1.5708) ; => 0.9999999999932537 +(nim/math/sqrt 144.0) ; => 12.0 +(nim/strutils/toUpper "clojure") ; => "CLOJURE" +(nim/strutils/endsWith? "hello" "lo") ; => true +``` + +### Interop features +- [x] `nim/` namespace за Nim стандартната библиотека +- [x] Auto-import на използвани Nim модули +- [x] Type mapping: Clojure ↔ Nim (int, float, string, bool) +- [x] CLI: `cljnim -e '(println (+ 1 2 3))'` +- [ ] FFI импорт на C функции (headers + dynamic libraries) +- [ ] Nim object access (fields, methods) +- [ ] Callback passing (Clojure fn → Nim proc → C function pointer) + +### Milestone: Nim Interop ✅ +```clojure +;; JVM Clojure: не може директно да вика C функции +;; Clojure/Nim: директен достъп до Nim + C ecosystem +(nim/math/pow 2.0 10.0) ; => 1024.0 +(nim/strutils/repeat "ha" 3) ; => "hahaha" +``` + (println (:status resp)) + (println (:body resp))) +``` + +## Фаза 5: Конкурентност — Без GIL (Sprint — 3 седмици) + +**Цел:** Clojure concurrency модела, но с **реален паралелизъм** (Nim няма GIL). + +### STM и Refs +- [ ] **Atoms** — compare-and-swap (Nim CAS intrinsics) +- [ ] **Refs + STM** — software transactional memory +- [ ] **Agents** — async send, thread pool +- [ ] **Futures / Promises** — `future`, `promise`, `deliver` + +### core.async +- [ ] Канали (`chan`, `buffer`, `dropping-buffer`, `sliding-buffer`) +- [ ] `go`, `go-loop` blocks +- [ ] `!`, `!!` +- [ ] `alts!`, `alts!!` +- [ ] `timeout`, `close!` + +### Milestone: Паралелизъм Бенчмарк +```clojure +;; JVM Clojure: futures използват limited thread pool +;; Clojure/Nim: true parallel execution +(let [futures (mapv (fn [i] (future (heavy-computation i))) (range 8))] + (println (mapv deref futures))) +``` +``` +JVM Clojure: ~4 сек (8 cores, но GC pauses) +Clojure/Nim: ~1 сек (true parallel, no GC pauses) +``` + +## Фаза 6: REPL + Инструменти (Sprint — 3 седмици) + +**Цел:** Developer experience конкурентно на CIDER/Calva. + +### REPL - [ ] **REPL цикъл** (Read-Eval-Print-Loop) -- [ ] **Hot reloading** на код -- [ ] **Error reporting** с Clojure стек тракове -- [ ] **Auto-completion** -- [ ] **Doc strings** (`doc` функция) -- [ ] **Source** (`source` функция) -- [ ] **AOT компилация** +- [ ] **Incremental compilation** (компилира само променените forms) +- [ ] **Hot reloading** на namespace +- [ ] **Error reporting** с Clojure-style stack traces +- [ ] **Syntax highlighting** в терминала -## Фаза 7: Многонишковост и STM (по-късно) +### Инструменти +- [ ] **nREPL server** — съвместим с CIDER, Calva, IntelliJ Cursive +- [ ] **LSP server** — autocomplete, go-to-definition, hover docs +- [ ] `doc`, `source`, `apropos` функции +- [ ] **AOT компилация** — `cljnim build` → single binary +- [ ] **Watch mode** — `cljnim watch` → auto-recompile on change +- [ ] **Clojure CLI compatibility** — `cljnim -M -e '...'` -**Цел:** Clojure's конкурентност модел. +### Milestone: nREPL с CIDER +```bash +# Стартираш nREPL +cljnim nrepl :port 7888 -- [ ] **Atoms** (CAS) -- [ ] **Agents** -- [ ] **Refs + Software Transactional Memory** -- [ ] **Futures** / **Promises** -- [ ] **core.async** (channels, go blocks) — голяма задача +# В Emacs с CIDER — connect и ползваш Clojure/Nim като обикновен REPL +M-x cider-connect-clj +``` -## Фаза 8: Оптимизации и Полиране +## Фаза 7: Core Библиотека Разширение (Sprint — 4 седмици) -- [ ] **Protocol имплементация** (като Clojure protocols) -- [ ] **Multimethods** -- [ ] **Records и Types** -- [ ] **Бързо стартиране** (малък runtime) -- [ ] **Малък размер на бинарния файл** -- [ ] **Подобрена производителност** на persistent структурите -- [ ] **Tree shaking** / Dead code elimination +**Цел:** `clojure.core` parity достатъчна за реални проекти. -## Бъдещи Идеи +### Protocols & Multimethods +- [ ] `defprotocol`, `extend-type`, `extend-protocol` +- [ ] `defmulti`, `defmethod`, `prefer-method` +- [ ] `satisfies?` -- **GraalVM нативен образ** алтернатива (Clojure/Nim като lighter alternative) -- **Babashka-подобен скриптов инструмент** -- **Nim библиотеки достъпни директно** от Clojure -- **ClojureScript съвместимост** (споделен код) -- **LSP/Language Server** за Clojure/Nim -- **nREPL съвместимост** +### Records & Types +- [ ] `defrecord`, `deftype` +- [ ] `defstruct` +- [ ] Java-like `gen-class` → `gen-nim` (Nim type generation) + +### Specs (clojure.spec) +- [ ] `s/def`, `s/fdef`, `s/cat`, `s/alt` +- [ ] `s/valid?`, `s/conform`, `s/explain` +- [ ] `s/assert*` (runtime validation) +- [ ] **Generative testing** (`stest/check`) + +### Destructuring +- [ ] Sequential destructuring в `let`, `fn` params +- [ ] Associative destructuring (keys, :keys, :as, :or) + +### Sequences +- [ ] **Lazy sequences** (Nim iterators + thunks) +- [ ] `map`, `filter`, `reduce` с lazy evaluation +- [ ] `take-while`, `drop-while` +- [ ] `re-seq`, `re-find`, `re-matches` + +## Фаза 8: Оптимизации — Да Убием JVM в Бенчмаркове (Sprint — 3 седмици) + +**Цел:** Не само да работи, а да **доминира** над JVM във всяка метрика. + +### Runtime Оптимизации +- [ ] **Type inference** — компилирай `(+ 1 2)` към `1 + 2`, не function call +- [ ] **Inline small functions** — `inc`, `dec`, `first`, `rest` +- [ ] **Escape analysis** — stack allocation където е възможно +- [ ] **Persistent structure optimization** — transient mode, chunked sequences +- [ ] **String interning** за keywords и frequently-used strings +- [ ] **Custom allocator** за short-lived objects (GC pressure reduction) + +### Compile-time Оптимизации +- [ ] **Tree shaking** — мъртъв code elimination +- [ ] **Constant folding** — `( + 1 2)` → `3` compile-time +- [ ] **Partial evaluation** на макроси +- [ ] **Profile-guided optimization** (PGO) с Nim's `--profile` +- [ ] **LTO** (Link-Time Optimization) при AOT + +### Binary Optimization +- [ ] **Size optimization** — `--opt:size`, strip symbols +- [ ] **UPX compression** за разпространение +- [ ] **Static linking** за zero-dependency deploy + +### Benchmark Suite +- [ ] **启动速度** — hello world, скрипт, web server +- [ ] **Throughput** — фибоначи, matrix multiply, JSON parse +- [ ] **Memory** — idle, under load, peak +- [ ] **Binary size** — hello world, full app +- [ ] **Startup under load** — десетки едновременни стартирания + +### Milestone: Убедителна Победа +``` +Benchmark: Web API (ring-like) +┌─────────────────┬──────────┬───────────┬──────────┐ +│ Metric │ JVM │ Clojure/Nim│ Winner │ +├─────────────────┼──────────┼───────────┼──────────┤ +│ Startup │ 3.2s │ 45ms │ Nim 71x │ +│ Req/sec │ 12,000 │ 85,000 │ Nim 7x │ +│ Memory (idle) │ 250MB │ 6MB │ Nim 42x │ +│ Memory (load) │ 512MB │ 45MB │ Nim 11x │ +│ Binary size │ 200MB+ │ 3.2MB │ Nim 62x │ +│ P99 latency │ 15ms │ 0.8ms │ Nim 19x │ +│ Cold start+req │ 4.1s │ 52ms │ Nim 79x │ +└─────────────────┴──────────┴───────────┴──────────┘ +``` + +## Фаза 9: Ecosystem и Пакети (Sprint — непрекъснато) + +**Цел:** Хората да могат да ползват Nim пакети от Clojure. + +### Package Manager +- [ ] `cljnim.edn` — dependency файл (兼容 deps.edn формат) +- [ ] **Nimble интеграция** — `{:deps {httpkit/nim {:mvn/version "1.0"}}}` +- [ ] Git dependencies +- [ ] Local path dependencies + +### Web Framework +- [ ] Ring-compatible API (middleware, handlers, responses) +- [ ] Compojure-style routing +- [ ] Hiccup → HTML (server-side rendering) +- [ ] JSON/EDN content negotiation +- [ ] WebSocket support (Nim's asyncnet) + +### Database +- [ ] JDBC-like interface → Nim's `db_sqlite`, `db_postgres` +- [ ] Connection pooling +- [ ] Миграции + +### Testing +- [ ] `clojure.test` compatibility (`deftest`, `testing`, `is`, `are`) +- [ ] `test.check` — generative testing +- [ ] Coverage reporting + +## Фаза 10: Пазарно Позициониране + +### Use Cases където Clojure/Nim **побеждава** JVM Clojure + +1. **CLI инструменти** — instant startup, single binary, small size +2. **Serverless / Lambda** — cold start е критичен (Nim: 50ms vs JVM: 5 сек) +3. **Embedded системи** — Ferret доказа че Clojure работи на 2KB RAM +4. **Microservices** — малки контейнери (5MB vs 200MB+) +5. **Scripting** — Babashka-стил, но native +6. **Game dev** — Nim има отлична FFI към SDL2/OpenGL +7. **IoT** — ограничен ресурс, нужен е малък footprint + +### Маркетинг Стратегия + +- **"Clojure without the JVM tax"** — startup time demo +- **Benchmarks page** — публични бенчмаркове vs JVM Clojure +- **Migration guide** — "Как да преместиш проекта си от JVM към Nim" +- **Clojure/conj lightning talk** — "What if Clojure started in 50ms?" +- **Blog series** — "Building Clojure/Nim" (technical deep-dives) --- ## Текущ Фокус -Следваща стъпка: **Фаза 1 — Reader** +**Фаза 5 — Конкурентност — Без GIL** -Ще започнем с имплементация на базов Clojure reader, който може да парсира: -1. Числа и низове -2. Символи и keywords -3. Специални знаци (`'`, `` ` ``, `~`, `@`) -4. Списъци и вектори +Приоритет: +1. Atoms с CAS +2. Futures / Promises +3. core.async (channels, go blocks) -Първият milestone е работещ reader, който може да обработи `(+ 1 2)` и да върне правилна Clojure data structure. +Следващ milestone: `(let [a (atom 0)] (swap! a inc) @a)` → `1` diff --git a/benchmarks/factorial.clj b/benchmarks/factorial.clj new file mode 100644 index 0000000..b8718b9 --- /dev/null +++ b/benchmarks/factorial.clj @@ -0,0 +1,6 @@ +(defn factorial [n] + (if (<= n 1) + 1 + (* n (factorial (- n 1))))) + +(println (factorial 20)) diff --git a/benchmarks/fibonacci.clj b/benchmarks/fibonacci.clj new file mode 100644 index 0000000..58e126e --- /dev/null +++ b/benchmarks/fibonacci.clj @@ -0,0 +1,6 @@ +(defn fib [n] + (if (<= n 1) + n + (+ (fib (- n 1)) (fib (- n 2))))) + +(println (fib 30)) diff --git a/benchmarks/hello.clj b/benchmarks/hello.clj new file mode 100644 index 0000000..aedd4d5 --- /dev/null +++ b/benchmarks/hello.clj @@ -0,0 +1 @@ +(println "Hello, benchmark world!") diff --git a/benchmarks/run_aot_benchmarks.sh b/benchmarks/run_aot_benchmarks.sh new file mode 100755 index 0000000..aa8c99a --- /dev/null +++ b/benchmarks/run_aot_benchmarks.sh @@ -0,0 +1,97 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "==============================================" +echo " AOT Benchmark: Pre-compiled vs JVM Startup" +echo "==============================================" +echo "" + +nim c -o:"$PROJECT_DIR/cljnim" "$PROJECT_DIR/src/cljnim.nim" 2>/dev/null + +# Pre-compile all benchmarks to native binaries +echo "Pre-compiling benchmarks to native binaries..." +for f in "$SCRIPT_DIR"/*.clj; do + name=$(basename "$f" .clj) + "$PROJECT_DIR/cljnim" compile "$f" "/tmp/bench_${name}.nim" 2>/dev/null + nim c -o:"/tmp/bench_${name}" "/tmp/bench_${name}.nim" 2>/dev/null + echo " ✓ $name" +done +echo "" + +run_aot_bench() { + local name="$1" + local bin="/tmp/bench_$2" + local clj_file="$SCRIPT_DIR/$2.clj" + local runs="$3" + + echo "--- $name ---" + echo "" + + # Pre-compiled native binary + echo " [Clojure/Nim AOT]" + local nim_total=0 + local nim_times=() + for ((i=1; i<=runs; i++)); do + local start=$(date +%s%N) + "$bin" > /dev/null 2>&1 + local end=$(date +%s%N) + local elapsed=$(( (end - start) / 1000000 )) + nim_times+=($elapsed) + nim_total=$((nim_total + elapsed)) + done + local nim_avg=$((nim_total / runs)) + local nim_min=${nim_times[0]} + for t in "${nim_times[@]}"; do + if [ $t -lt $nim_min ]; then nim_min=$t; fi + done + echo " Avg: ${nim_avg}ms | Min: ${nim_min}ms | Runs: $runs" + + # JVM Clojure + if command -v clojure &> /dev/null; then + echo " [JVM Clojure]" + local jvm_total=0 + local jvm_times=() + for ((i=1; i<=runs; i++)); do + local start=$(date +%s%N) + clojure "$clj_file" > /dev/null 2>&1 + local end=$(date +%s%N) + local elapsed=$(( (end - start) / 1000000 )) + jvm_times+=($elapsed) + jvm_total=$((jvm_total + elapsed)) + done + local jvm_avg=$((jvm_total / runs)) + local jvm_min=${jvm_times[0]} + for t in "${jvm_times[@]}"; do + if [ $t -lt $jvm_min ]; then jvm_min=$t; fi + done + echo " Avg: ${jvm_avg}ms | Min: ${jvm_min}ms | Runs: $runs" + + if [ $nim_avg -gt 0 ]; then + local speedup=$((jvm_avg / nim_avg)) + echo " => Clojure/Nim AOT is ${speedup}x FASTER (avg)" + fi + else + echo " [JVM Clojure] — not installed" + fi + echo "" +} + +run_aot_bench "Hello World (startup time)" "hello" 10 +run_aot_bench "Fibonacci 30" "fibonacci" 5 +run_aot_bench "Factorial 20" "factorial" 5 + +echo "==============================================" +echo " Binary sizes" +echo "==============================================" +echo "" +for f in /tmp/bench_*; do + if [ -f "$f" ] && [[ "$f" != *.nim ]]; then + fsize=$(stat -c%s "$f" 2>/dev/null || stat -f%z "$f" 2>/dev/null) + echo " $(basename $f): $(echo "scale=2; $fsize / 1024" | bc)KB" + fi +done +echo "" +echo "Done." diff --git a/benchmarks/run_benchmarks.sh b/benchmarks/run_benchmarks.sh new file mode 100755 index 0000000..1968191 --- /dev/null +++ b/benchmarks/run_benchmarks.sh @@ -0,0 +1,88 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "==============================================" +echo " Clojure/Nim vs JVM Clojure — Benchmarks" +echo "==============================================" +echo "" + +run_bench() { + local name="$1" + local file="$2" + local runs="$3" + + echo "--- $name ---" + echo "" + + # Clojure/Nim + echo " [Clojure/Nim]" + local nim_total=0 + local nim_times=() + for ((i=1; i<=runs; i++)); do + local start=$(date +%s%N) + "$PROJECT_DIR/cljnim" run "$SCRIPT_DIR/$file" > /dev/null 2>&1 + local end=$(date +%s%N) + local elapsed=$(( (end - start) / 1000000 )) + nim_times+=($elapsed) + nim_total=$((nim_total + elapsed)) + done + local nim_avg=$((nim_total / runs)) + local nim_min=${nim_times[0]} + for t in "${nim_times[@]}"; do + if [ $t -lt $nim_min ]; then nim_min=$t; fi + done + echo " Avg: ${nim_avg}ms | Min: ${nim_min}ms | Runs: $runs" + + # JVM Clojure + if command -v clojure &> /dev/null; then + echo " [JVM Clojure]" + local jvm_total=0 + local jvm_times=() + for ((i=1; i<=runs; i++)); do + local start=$(date +%s%N) + clojure "$SCRIPT_DIR/$file" > /dev/null 2>&1 + local end=$(date +%s%N) + local elapsed=$(( (end - start) / 1000000 )) + jvm_times+=($elapsed) + jvm_total=$((jvm_total + elapsed)) + done + local jvm_avg=$((jvm_total / runs)) + local jvm_min=${jvm_times[0]} + for t in "${jvm_times[@]}"; do + if [ $t -lt $jvm_min ]; then jvm_min=$t; fi + done + echo " Avg: ${jvm_avg}ms | Min: ${jvm_min}ms | Runs: $runs" + + if [ $jvm_avg -gt 0 ]; then + local speedup=$((jvm_avg / nim_avg)) + echo " => Clojure/Nim is ${speedup}x FASTER (avg)" + fi + else + echo " [JVM Clojure] — not installed, skipping" + fi + echo "" +} + +echo "Building Clojure/Nim..." +nim c -o:"$PROJECT_DIR/cljnim" "$PROJECT_DIR/src/cljnim.nim" 2>/dev/null +echo "" + +run_bench "Hello World (startup time)" "hello.clj" 5 +run_bench "Fibonacci 30 (recursive)" "fibonacci.clj" 3 +run_bench "Factorial 20 (recursive)" "factorial.clj" 3 + +echo "==============================================" +echo " Binary size comparison" +echo "==============================================" +echo "" +BINSIZE=$(stat -f%z "$PROJECT_DIR/cljnim" 2>/dev/null || stat -c%s "$PROJECT_DIR/cljnim" 2>/dev/null) +echo " cljnim binary: $(echo "scale=2; $BINSIZE / 1048576" | bc)MB" +if command -v clojure &> /dev/null; then + CLOJURE_SIZE=$(du -sh $(dirname $(which clojure))/../ 2>/dev/null | cut -f1) + echo " JVM + Clojure: $CLOJURE_SIZE" +fi +echo "" +echo "Done." diff --git a/cljnim b/cljnim deleted file mode 100755 index 91a99821b8893937cf68b9c24c54feb8caf1d9c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 353560 zcmeFa33wdEwKhDmJs`7h=Cqtf>?qlEcW-l=Tvp~lr#=E-@V`S zKmUUg)7@{KI(6#QsZ*z_t7{g-k3DKkRaM0Pjg5RQ!l(Mf6=^gHgQrH#T_n;NX^3os z|Lzc}i>wE99R4+$RQFmlRuk;C+RzbZz9y6IUbi)L_G%^MmH8_DRo82Zy+&&Enfas! zjr!~goIFYevDa`uDKBFLb*_y$Tib|WGQGOlRTJt5g(W}ij29J_&mZ5Je zzZQ%l?A7X%RQxx`i0NMIkjB3{`EMlR=5wkQvCl^udV6IV{97OYcwec0q~TxnzUHcz z$ceAdJDd4j`RsF@p|e*jXOqd-^|4may{bqUZwige?d9g zt7(?VUgys|^ML*KI{)lF&YwBEb^adn8xGjxfc^Ho@Pa+}kqnfJWzZInKCU@3rah7) zX7lrY3;bg(nLc;ZHG2$v<>-bxS0DP}A3ERNXUA)LziD|e4{7*EdfJZto?3I@dnw!1G|~C*y?EZt!T5N61-zpI{+$Xq zUC?O!PXb zf4>5ruYk{~pywkh(7#*(f4u^}e+7I(1$@^EIQ`CO?f6jz{MZWkjur4#74QWW^mAtg z`Y%+lhZfLp8re3I?O-ns{O~Qfr%di&3;^kS9r~*co^$ZX^mC@oJ1cVGw0Reu**ZOP z;k>hF&W^Os7I6C9X;aUROq)M*USwA5>{(OiM5a%hJNJUQk?GUVZ@q8^i096`;Oxj* z=TDnD7YUK+7tEP9J96PA7tWhDi;3s+Y2Mivw9bo6KmUTW&YM2-{ArOh&Va`|>x>KM zO`SXMj9F7r+Vs}hek#bxY3|gS7n1a>`BTrBK6Cce^JiWCF{M&tl+kN)jKXS$~ zCrv&B^_X_fOsHb+Nt2H_|AN`mPMUhARR5e=7tA)*KSN1JB}99Tk^hawf2+*BBJO{g zI&KsS;!z)0OQ{0o7h!95+`8wQ>*4OBkzEaToji|=>|>tqmFKaML(OyR)-%UbvIL&_ zXX6w*?$*!&;BU~xFBZ=quDIvmbCz)Ai9|{j@amc6c5mrxE8r6yJo1S0Z*cG$gC`uk z&frrVywTt@9DI(!=R0`2!MiHpSqJYn^gRxKi@|#xJZtbi2mhJDa}Iuo!954h=8XOw zJZJFgbIbJ~S*rB44&G?+i4NXk@CFBOH+aIqGX|gH;8}yuaPWe`=R5eY!Mhy1W|_+0 z?cj9=?{V-(gZDXjMA4c_43IfEx0ykPJt4j$>(a%VVrjlt(T zc)h{99K6xs-45Pl@E#Yp_V3^=1|M+nIR+ne@OFcH4xTZ1$-%P*uQu~qJC5`kyw<^U z2A}BQd4o4Nc){Qa2hZkJ&r=+{)?$BpXlIq&#Rmb4&Gw$goDp9_!I|^3@V=) z4qjvM`3_!Z@Gb|hH+Z*$HyXUh!CMU8=iu!IA8_!j!3Q0@*WjLm=M7$R@PfgsFDTdl zu)%8`Jn|RS&qN1rF?fT6=M0{3@S49WpD7MrXYd&gZt3Sc_#8vu<>2iG?{@Hl!FwFM z{%$#>))1J>)^KBi4JbdZE)}w!$0BR z?FOIX;Fiw}2e;+UcksO7)8*g=gLgalu)%vAJok#xzk?SHKH%Vyg3=E)?5VPjv8t!5bWW*x(5V&;DKIpW@&JgU@jAnw3gF-@)q)-sRwp z2Jd$8c7yjgc+TK`4jy@3%N=lVE6<>VHyV1c0$!?sSI;fif6nl!b?}-uRGx_rUT5$I z2X8cZ!ojl!pW@(=HJ1%uCbaBC-B z4sPwR+rj4)Ri8Z$Ztbei!Lx>bz`<>~gASfI^qzwk3|?~ZVS`uCE7yPIEtRv@!D|dY z(ZTBs-r(T%22VJ6qrs;*c*gX*84jK`_Yi!6U1T{X2N0!TTJ%-QWWbZu{4ugXavr=i=sgFw{7Vj=G4$0Jm+L=g@LC6tykqR&!L5E8T-?wn9Ng+{ii2Bw znBm}7Z}T18+C!IvTY0)2+?Lzp;I_T`9Nd;W;NWviy#^iJwu|TBwp~gN-fs9*&o9@1 z*5I`cUNHDX2enX0zT2fZM_;Cyx#CnIJlK(ii;ci84liJ z@c9mI`)QYh+y2$<;4P;A_BgoZ)92up&wzv5emdyjc0BSNJZt1HIk@dF)t8m)-|Dm0 z!EJw>SOHHsxQ*whIC$2^`3^p3wc6o)2X8lcmxE^v-tFL7gZDW2u)+HrJW|qf2OPZK z;DZj{WN^>H=NP=?;2DEgoBe72dcMJH9Xx07i4Gq5m&)1T;Ee`PIC#O}QyjeRL*+BW z!5a-e-@#i9-sRvqgLgZ4oz=gC=dAu6ykP1(;NW#e{_2J0_GzE%9K8J)eb3(D;Pn}; zZ=-|XZRlGZeDk>SneX5=hEKbLpM12^XB|BEb;bJ}ywUKQ2cKj0gQ#~#KOtQ9Cn_Atg>dc-vws62d+-{lF7LcJ@=mZf9)zY_nnz7GLlps!nLNFqb7u#mXd_p5YDpgUtI`i8TM~t z2)DkKY4sr-8+ZJ_h7i7ik47SmA$-FSo(SO^h47{jzHtbj62dnL;VmJYd)4gUj1X@3 zCNXVJ2;bBa<2pZthxhljhw#lq^j#sG`(^E4CWLQkL0r2-xY@cZso4--8=~(C;ai39 z-VkoKa7$`m2)A3hnUD+N;SII}A^eLW{`nBTT?iix;oFDsLI|%5;a&*eF@z6?@SQ^V z=eM70;ByUpu7S@r@VN#)*TCl*_*?^@Yv6MYe6E4dHSoCxKG(qie>ITkSXG@!Memz5 zE|TcX%^R~epXgX#{it8zwfp}ONo#j`9RH1N--tUt&tQ77uoi#2EaNkm#)^4)evr>x z5-aB9`EEXQDXiEl&v)>di)zKJJpY8xToNm0al+RoOD>lmWTt0K@t5`43=kl3LUd1|jK9kQ}>MGXA^MCP~OI*c> zJfFa4E^QTu*8te%>wM;tRs+%q6Sh9C_Y=&s?f1w#f4sK68nx*d)&%eGSiCnkqKR z^Lu>el2ox?p5Nj#m!gVw^86Z~xdc_Lk>|hinM+T_h&Eku&s3T%e)*|nl}OF z_B9aF+{Ujh#I|oI@uTJn`YNH{|D#6G?6xM698N6#$E3v4(%3}Rip0}v=4}B68yg1I zYYWq3+aDR^e?PX}N9~8cjl7Z8-J25~hrV?PZWGDZ=WUco9l9mz=Iw@7^v=eARzx2I zUDatQ+b^H_ix(rY8F|n-Y7)t|QldXs^TgVj67*US zUt@_=&3%xkH?iBYM6$W$T|zR*XN2ikRfod774fwZpk5-?*g$3{plf%<*T(LTuk9GF zT9j+uES&mrRw|@W(?n#dPb6PRBv*Jx{RD+8pE@XYUcLVSB}0RKlpBAIi5>DtZ>6eO zD1_+uZu@a0vLe35GN>l@2l$+>_!#$+L<`EW43*!=1(g705F%=Vs6UoD?SrXT>P1YqGM5yCmiH>26 z$uZsspR8SLyFl9-b8BvLhWB~UF=o<#A7{87x$8~SLi zIGJ^OqvdFa%HC+!EtR}c->p_cIGF#gH`)}LU2k--2vW%#t-LwxjWEpA(Y1P0Rn;&< zs2O$N%-G0i&SqV<^&~A`O)hL(50n|F6zO{NMeq}q^|yzLtxZ|x&F4e23jYWCdm3zA z{e28sD|_?XZyHs9JHSSyOaEW$?+0R=(q+T?yBKiLH%Haqks@}b(eUjXE7@NIRPZ0` z?;2>s)!*+SYi0eNt%5j}@m?+c&*<+sw!=vK`zm0kNZ0{cZBEQT4YP&Epj5>Tfx6R_?#EZm6WcK2+#G*59Vc z?CS4e5u~F3!?97JIM?@oAO6qi?@6}9Nc(#L@W0XDBB;2s{`OF@RZ->s51Rdt_4hQ` zy88PVvR1ag+ZT_jza9ShKcl}NupLI~?_$7#{$v134tK>rAgLd`&FT_OU<{LR2Ui5j zlRxM>b_}CTJ|g0&l3Shrga}t0Ii@-ilu*w>5tEw&rX!4=OB$QtJ~g=j4vY{uOm+`z zwS%1W%?Qm9cqI{GFv>_x)*wuC+*}rAm6wy8oEP2@RO0?-$Q;jWOp6kqXNgxVXC)~} zypTxm$2b>7meb8sIWe*NwL~&6n3ax?g)yAMq_^l472%c$N~c`ALHOMCWc-cT z1MxR7J?&0(C(;u-CXGeIjH^kcC+AR=;Y8IRyT-&GiN7Ha9dA{UYU9(Cs=DjQ3Asea ztl=@mlUW&xI{xBXir0~=PcBWQo52JTaaF#ndg!=}$X%5~6{w;?Y4LQlMwyK%D^jwh zo7SSR;jbZ_Bze^UA|6|0RbMSNvOBn=AWE8UWns`^zjqMAtf1Lz{AMQ^b*Ihsy)r5q zhnkf&sgAZnAQ#evy{?deOl6l7X^G8 zLM`K6djIzY==U0a8Hr@P{=i~B%US{YbfCOm$V81jYs2Y0sIX?w|5OSNL67z zH&L~bwYgHwy{KQGsrok{8xZkS(JpG=`DSgIUx$cKA_PrJ z5?kp((Bdx^;mQ?SG~>|`P+k7qwVp00*}4*zQZ}%qCa(mYr-Rh-MYRZ5tFoS;{%r}$ zMmOm9Zbu0LUDJE9O{LR*HK^amXGv8n)Gtn(N@Eyj-zCEn!nE*b57o!UA#qmbuC^)wtB#Zy9aHc6}hPleK7C4DZdeUO|lK8*fVz<4CT+HvQrN$kRDzuo={lDrj@4Wt>^p|Ao zy=c|V&-A-Yt=?8JsU3|4ZHNV8E*BwE&Uk#i!_{A3`Ax~Thmx)A=eKF$w92zW za=-$dwZd66Ge~xkF^*Pq}XFp2jlkrb7-b0{I#Xr#^KhsD7nIOeB zfk4l+1d2f^jzBNM5WU+_Gul;u`~y-@Kn8leelPT#ia#_nxA~EqNA_5|X8p*LEs=zt z{0>A+YLFfpF~*AEAC&2eic(>ysDsui*O8QK{gHBg*T^&9T=^_=ee;J~N0#t47)0V~ z=}3|0fMqH^LZod1k$!Q;x>WUB1Y_`F!~PQEd;-u>)R@~fMkYQ(rs&RGa_>f{JdUAq zIy&b7uupG+XdeGRp&A;8vulR`ex8hE!MEA^@P(^-S(k@Tr3%u~4w0;LhCZ!5opN^0 z=Tm>@99%%lhHXcI=}x}4q>qh&t@kKB4MY8OJnKuo1j`^M=V27Klf@v%10YVuWsLCK zGS+!D*G{#Z!$B?YdRW7is|S&18&NibqqrB!b2{>*7LzRkrz#lpi-yCIq8!b7A+kcY z47AYg7e>OH2WW$E6Nysoj2NN?)g$|DQ&Bc2Kk^Fd4 z=`kA3|8NLs>MOU)2 zkh;h9HM35Y-b`Qfrn^v2ay^ zx=B7fQQY5s++-<`r0{2#Atq3oT>cV9FKi90S@^ z^9Qv1*l)1R0}rkRUMfshQtm`h;BLZ%o;%DW2QO;eHE3;tX9;b=p}m0~yLlz&xmqCN zF453;rNHN$0=E+;&pAw*rNF$QwFO=!!wQ{XSh%l9fzQ!1YJqgTjZpJtQXrPV%4+y4 zVY1v|@)pKebOWcrg+iNiXqSRE)x10v&zS=0i&fI+q3zoNIf&`JD7T^1k zu08>4?nV)kLyn8fl`I{K&;FCBg5M3EE@Y_$Cv#bLDOU92-& zQ(ka^>;?#cY2unTgfj7bw~CX$-dy=i$|6<;87;;0H!A)31>5oICWoHSpsyO5A;dQ; z@yQDgCh;u}F`v!ymzvN;aIL5@nk29TiJ;>7Tb;aoreGQGTGT3}orSCDA)p|1u3<6F z_Yz474im}6>}AQxi=eb0tERefdk0$y!4*WzA}hN9x&oRv!{)vB9VqQ$wsD}e>@}1o zrCd|!QvN^{&t5A@QWEL`A!Nn+L>*6aX#Rn+>b0JVN5EpU*&j+~zE#1qI4w41_Bz72 zrp=YleT@)x_xK`C5=6KcI#= zP<2QR`7Gps?R?U9B%AlgDqa3IbLBJM#OxlwSKmwZsVc@mBDawP^ZJRhH0JeFhb5mw z=5@Og8}s^^L(Jzg^ZEit3%6urUO#v8@>$JmUsOD#zzV_E1)@9~Y)!#T1xEK7f~}L^ zhFZ6RMxa)_W)h3?gRM=G2*s9Yls3}Ral4P=c061MbH?VQZVZfOjQJfC`j~;gT#?w& z#ARIlmI41krfWd7$G+n*&&K+Nru<%N{CCv)HMHpm5t-e+hMK>ooBlQe_;~DAc%;{K z%J*7V{O?HqyC&~}pdYhhz8EWMJ8uo?K>{Ej8VX*;%LmPGGJeC9|1c#}QF?+~qQY6t zykyNB+MR>S_sh7+$`*eC#+++$&|M;Rd6g<+TpE!jtydxJt14`b(Op&S2Gr+OmB*x6 zd2)deA$f>G9{7cCN~47xOIb~eU!XigyIGMQB|96n`pvN%C|dSO4uUHBV?u|we>#!m zTHI46X$6u{ho`#YPa34(N1iaqAB5omHKJm^2G7NztGpFXk1~Cr!yA)~FE?5LC|Prw zb-Ao?9Ec`N!aJ2`-kF&hpy#-}@sP>>1hS{%n(sOqt_f|D5|%hDR>Jg5zNeY5`JqHA zZ6YB?F?hMlt*m__e4mr~jv(`M$c)qy-oqw3yef2}Uc>g!p%n4$Vi?bdP|oZ5LQeJm zSBepTNX;y3Rz;sqq!yP+1z(OC$x~i8r$G#1%QdrkAX!06LNMTsO+5J2>J$I|vzwRK z`O6wu81v^fuNhM5Jl;$>Sa!E%$>xdTN{%6^C&oSK+LoEu~< zLsoLYywE`WAv4lC0?HfbP-7yR8rz}?C zxj(*=6)ZC7w{$#l4(m5mMCVYTh<5?6h`)?~FR_0-*%f~YmzPw+ulV)t)%yMpO$Q36 zzGpxjt=UUm@t51%UXHXj8fo5Rsgq%#_o*pZdzRoYDcXD4;kJd(?d5Vky$@a^^(1Ho zJ>^+{NM>?fJv)-QXZliNI(zQUn0&n2!+7wKB0Wxh`;w6{cX@q8B^7q)${_7685e37(o(&1vB zmCKXrRk3y7WGnnLs@YObHuGFU3q*=H*lbs6wguOBM>Jd7$!4BQeCu6ovDtp0*(Mzx zGuf_ovYF?Se3iDF&6d$@w_QXAlTFn;QHDa>;(8*?=ONeJ8 z#oya(*J-vlVTM|_#ZET!Tml>^F0$FK$0I`Ivo6B7NtjhTdCYSOOd~@lig?|cVZ3Iz z(aB(*IZQ^d6hxjkIV9$}1T|qAoO+c$#b3(^ZPQCm#@gn}^hP>xNVK#piEqoIf2kb{ zr#r5yYYft>a_Izj4|=kvEJSsWPi4N2O17=yf_i$wC0j}UnnY{~XRzEVWWVxRxC$#I zm;*g5bD-pXjLI?pZonm(-j5IIXfxi3C6b*}^l9fKgta#r`G!8IbL8i zG8M!X3mSYDiRxI4-fkJ(5;c>|j<$hF>n5rAhC0RY zuwsnn=lTg}?zvUHnkMz2zZ9l-{n_sF(kEJK6Xi3H903T@M6sCEWbpzv|2=Bp|w{FhicFnM+< z%d;L=e@1Vl)*#Q9m}i8>i6YEpwL03mMB`Us-5hI(coQue@520qfjVAHn{{Y(RJ8v^ zWX)yh{`jDuof_|^yMRiuCL14AjjP(Y2rhd@Wy#j8z~cpc^Go0qc#Vat>d*%0wk29v z^1flYEcWD*F7T3$KG-NdwVLLfj-EeBo@)8j-&unTB|^WgliNhPGr}ZDeeR1`>)7LE zKFE5r?ZC{ln`%`HlGma{zpSW~tMetIyA!o9ceKbo6cS|^De4I=Z1dJ)Xp3F(DfObX zdka%o-GiS#Id##d7e9WdKP%fBlJy$0eS{1jO9f=ffe_`)eB{-CMianx!z}nNlZ$IA z(5ITWmbV`v?C`BNB>UsJqwTK9yhZ~Y03bE_F7yVx3R9J*I<*1%y#;GGgKDXY{e> zMsuj>>eAAUB!LH9hc~_5kmA#0aO(+*JsbelGR_bjf#;64+p(O*Uj#Pbk&^57k^MYt z2;wAYzP&;}zr|1;MJl{}9#z9nlzhuQ8gx(&HbT?;-csHgzW2Q)$dJ$s1%#J_vCbUy zZ~Rq%TwiGQ$GZTsb=vhRdOHT$FrX9ZXuCyvK00|f`Yl!grRU$0j9-_EU&lqkV2z6& zuiv|b#oKpG@qrNAuY;}dqbuk#t^h00@d0%O?LwP^-;fm{yGAW<^s3^8;{Aaxv>k*G(5ja zp6TfJ2M1X&)S#MVt?j0_TolJ5qxO%g{EA4u;V@kbJv3Bk2Ue(tZm@MZUW<6>prDA3 z_~5vp!<+zuj<)CUrNH>07n}%iv?!qSdwG=k#D3TAV-ELq9m2=#G{#6-Uk z)c(Ijj`*OJZfx*iSy_(U3BwmojafC2~L;`liqnF&!v$GCp?2 zj{=>X1CToDJT|BrkKqVcVQ;llxr+2gl%S1DR;b1-=1U5W zMrE=z>T#K&Z&v$PPBg1vhH_JEReUbfVt$HW`4h!28Fp8bie&5dHzn_2UFscOm#Q^HcaZ}K zHkJ1)@8~+zJ6-W#z-60ASD3YO3 z7+J85LGA<6Cj-*%m?z+6XJ+WjG*A}(J5rlMhPJTiLk2}GV2gg3P^!6r=IPb1#)h>4 zY@Q`TAZ7)+42Ey9H&b17ms;udcqv=n!1aiwj4+P~3sCGP>!y@17eKv+Y8fEE+Of{Z zb+{n$qa@~$01Fxn?PHo9F$F))3pBr{NnfGq49|y)ZQx{U)2|6V@b0~_^hziKwqAoW z{P%0i@fF(9DX6ZTx4LhjTN188tk#z;gSurXa8Ht)2%IyI;Jd_P67WK3@=8 zmNnA629?hU^Q$*`|HAZ+IKDspgtk}w&{*J{pcxoIgj(dqQ90^&po`V9{1I*MxAsd? zLR)VY*94@;zhcU)8rsiR_q9=(28#N-rb{m|T;v@hzgv?Re;Uy!|2KrVTp!a`be_R_ zn9OgA2oJXqy5tLD#bIkeT)77}U5$qCZa8?t0g5xvMLyewmkz^uJ2VN)kWU;lEc_8kv1dKrH)Hq(e6hk$8B6AatNvzuX?-DQ;# zw5qr@e27)}+djXuE^ajZ{y~1>V9RlzKOOS@%{zwanBrqF4a@XhGSCpqeaqB&5^JV% zzelpg5{ZqvcDA@FpnYFhGV{9X_<<3J0S)W?Pv*k6uP9REEvF`bdCM2(LzC6E<7z%D zms^5cLFwy)&0||XPVWAK>%vvT&_wwzTzuNqSqmYO`L50_C`>v!xR>}RY3FowuRZ07 zJ`ZKi!G#@d#PS-3f1P^oNSPuwGQ26Fk3m$Mt76)9{@OEL_ z06$2JExJ@kQc;8m$4!B{hlSC~9!5!3k`}7qX(Ea)C6`OHhZJRIpK6;FtVRXPIf9K* z*>mu^c-h_B|TKA`W|SGTvVjAqT{xYgp7W1YXI9cbopS_#%4 zL#FHmyZ>)gw8rYm#R(|ZkwNx`YIbQLz5Q39GNMOfAfQE=b>y?K_MUDOUeuUw0awMv zlS_}`H?&xP`#_o)q@ngIVhx}Dcf?9D+n`#-FKdH(Q?>Du1~sYQoXEo2MjYChcY`1u zAN>SvX!CCvq>l}!*O~kqG2N^vo672_Co9Vknnu=#4q;c8;oJ!I62jFBwc*CXOT-H4 zQBaMLOU2DX!-?E;jQ2oVh^v7L z8fU=@X`G(2#))-aN@@;rfkAFMiX1bIjGIZ?$VQoM(l-y%%Nk|6N#7z!AGJ{?FnxqU z3Q9#{dj}n)KPJ!)z91~i)n>)14T@uI?Ay>(Q^r<7dYQO3|JFhJsN!zJ^bz6;8WMM1 z8?5(dvjk4wY@g&OyS;Gcn}3<^8&PIDx{Kaccu9W&%)#5IUz)fCtL9Ka>(*1+($P0} z6GrH0lY3EI--9$}`tGGm4kINDvA=n4G3hZ4<6U30^ zZ!MU^&CNI;1O6Ugou#9MM%P?*A^MDcrfwIef<`0*sJ9xfERTN%o+9wnSlVNUpfj5y5gDR0ZW zq}HBy7i6$s|1MmWW#;6=GhkmFV4RtP&f!_xEU#)759fcKmNbt*SyF=$jnhLk?S`;JdC!An~VPGCfNyS}eOj zD~RXVvhp3KmDqO6bhKA9RT7hC=RKh9TP`L)jE2VAH9!4A+d9jCpRZ0wmS69bkBy0m zv+^NDy@A5L{YOY}_GdVKQ(DzJu-j#j9(}-r&n(FIDfR;xIiRTQq@>xuP}KzWt3ytN zU9GTf?&s&fb)1xBsLQ_AVVTHd)go0BFM6MB$LyqBp^=y$xZ~t%lqn7YlFj**v)^mimG_B`w zy{%w9oQJ_2Wa<<;NaxebVI8z-DkVMPOK;F{R8{E-TMEaf8k*6M)v{jNNi0hWF$d{< zWTEndbk00ZI2W`PM>$A`CH6OBaT#<$0hjsQ;y6@COPz3zfIL*^MBb(MSE_#R?^6-I zd%uS@n`Zp?E}(4mgxXHi0;$1>EgRxctnx76dHR6vQ2+Z4NbGkBcI@l-I(~`?Z~O!K zhG5ZoGNFrSLCH#L)GGtAJew8oc)LmOGS^(M(EBrD1aX`rGu0al{wW@I(50`&Gf;3h zo6nDmVx3>B67~{gc+dRAWSEJ=T+r6+Q|QzEUh#*Z;zB0Biqntl+qsI_k=uaD=|`C? zGGOZx%TJ^>j{gbzPfa+A7aK~*c)zc>kI5M@>Ml>&N>Vo7MssdEBIm!j`#&|g9XY$o zbG{-e8^4RK`6%-}UdqaRMAB74v%-lTBdAUf4eGblVbCtWhZ+dqEpT%-A+ebp8GW<5 zzCeR;3naagYD`DB+5rXYM+9B)Me`;iD-;H7=TslriOA(ma1MP- ziu#T6k^TDHOZ^9+itKiBTOoYBh%+1nL+7d0U1*5=7n0u@)t#;CNG!jyFB#)5{a_t$hQf zOFpbSm0%$VT={`vUKF`_5SQctUTO~D#($%^t;&Gj9liC7qJnNzG{b7a#b&`UO)rFa zw8Xg1ZutSpvyjWI`SaMoEgoYYu=!d)*rc|_ta!1Moa$uBrg>&Es!YC2hDY7TTeLWM zJqKP`))>S(+iU6{Gk+$eOCWiY{0+}5;{SkGe_RTZWvhPg%dAx#dl<`BGVTHs`(`G+ zm@iSWWe^HMu`v`2hXLbTonPv0-xdD{l=u%_zu86KQh+K!9mW6Qu$=<7@IOI@Qz+Ss zloZ#aC^^?==GF1>Xpv!gSlE4UW!cNsg=Jnc`Tl6GeCA79f17Y-r6O25j%Qvcm3g8W_qPz_O-_{zfw<7+w`7XvF2Qk~5$YusoG!t|BtYJYl5NB|84CP!Yq_luO#O*3Qc<_VD5s1g{oZ$P7E?n^6VDwk z(te=Q#(T;|{K+kBAbfWi&m9rUf3W8Fh#x8WS89F*~EVzv3?^oU=Vh@9l_4giLxemPWsyFjCThLKr65I=P`32BK$}6S1K19dvm?*1DjqWVopTNveHGIEa2Tq z{c4GjUP_dZiY1%pL38chPbDeHb2>T;yT*}~t=YRjk{-!o9_HNy{{=LUk&P~?_W;e* z`&C})Y&vrbww4=tskh=0a`R^4u(0CCxI@aISs?0ukPMAItPzLjtc}Gw`>_N3l}nW>{~I3i>4usx12 zGzM=u2d=9b9IzhbFZkaxOQkquWSKYBk6Nb3ugy{5p~x|`Uh!}693p@T$FBT>>7~?$ ziLMaCKpCTlV`{{QvZzpyjQO~peo%nCHje37-VQ_QLd4V#3;BT-L}PhlOH(GqVw=F8 zb6}W`XK5@CL16E4o<(8IlO(8tZyZgmJH~&@rK7Dqa!DQJX&*(4`PpdhVfuO5s@U(H zErEDoHT)%Yq*bt%Nf#pfZ*`bhM=9sB6w#<}RI;4`m6wLU1A!!}L%$G2n6b{2$QVjE z0W!#DANj@~2iAhTi*B`ynxC-H+v+%R1~Z|gDVBJGppVyS{|x}6YI228Fat$8B^<5z&R5#+Q^j84yOF8UilO>6a*E;vsml#fpT&|?SkC#$LFO~}=!Ay-~Q zuCkas_d^-GU>ROLM6mNOdlW~>9)Dammrh3$Dh9`;D>gHYE8(z61V00V%uxBKLLzTF z`QTo3Dndhe>tC|xjs>xeMZw}cJj+Yl)O~yhh44$J@9Fr!G5t9v+HduZZtLY>ot?!ZWH#0B$%&_S7z^iQTtGfu@eg^2uF0$ygtDF^VPIKdn^Mn` zulYl4whZ$teu&ng4T#N#maUFDA9-|k>S0yrf@i5d=H=5FoAODPt+L-y&9t+b!H4#W z!n7eeavM_xKf{Uuc(ubTU{=5JJr5#xYQnv|$h1Uy{z=^XkxG|q6ImqjtCEOsCFroZ zijh35R?dp1Ja&l-_lNa~IOz#D)KEgMXB|md)BE2gK)<*7H!7P zll<4Q$tREHRJ0!>6)c1Y9l?1gCwIY=J{DG(ET*INdRrlgT@M9#1FKLk?WwtoRaXVk z5eB&VcvdSyA+oF!FIf9Qo1q5%-s>3pM%oX50F3dM`2AStLK1`FrD_NTf3-)|w_4p^ zV1T2VQSLIE8^zYqWrQsy)>*v{GkO~WjAlk_XG+tv;b=G$pJuaIGpdd8NmoBFSJh;} zhc)Sut8QEq8=A`AYS*4p8!zZX*3^V{UL-~kGpXUZ%fXg_?URTaQSQ zo^Z-06pYprqloGKXA98p{rOnmdU{Zn^+O%2>R}_^zdIKCpsUpsf^0R&2Iqw}e0{rG z@~3O%$3hEx&=p{YbgCs)5AMr7 zv-mwey?1|<4=;K(6c|7ke@SmE3}s(Ri@D}MWZ%GhJxk1;BUtOv_V&J~BX(#N39Ul^ z(G!%@YO{_yWG=Z%%XwV;U>|-?uR3ukUp?s|sowS|0uCWK!m9;E^q`0nlrKx$%$6px zwTyun=+J+9>1e*KMdWUUuAw{h5;z|YCX7*$<2J=%swmnxv@9?aqz;q*i;x0AYJ1x| zk=7V9{g|0LW|itWbbr?jO@8Fn2pI=p);S1ww+2UpxII^IX$J)wI--YRkrR#C@7Au|6qsV9hewRHs2^VX~h>xW*gAL@(%`Ez(T< z!4G{Gw51A0vskgt2q&g8=CMP7vgWtdtLe6|jZ`C?AUx5+Wf8XM(%0}4nR%IFap6|B zJD|`S=EZuR86>!MqZ$8f{#N8)q50pCpV&-|!I74~-2}h3fz?I)pd&WRY=E)DA1Lt4 z7+{FCLd^Oy#k0^@-kb3AL4}+A3a6uoY7JT0zi9AOp%3hcpXVcAe!LIidi79V)Nvfz zOKc}E2S{;dJMSn`-l&wrVoC?bdt0|AdOgu_Dns5~IjZDu@-sxpa_AmGZ`KT=Rc|{p zVEM)suFH3|0<6o%>#WPkNtNLqEwC3qUOHZVNuPG23$6E*-s&0gvt(+CgtCwA;Ce)hL;n3FBao(niQw@}A`s0A=~AYz+0&NtS0i zx=f>=3jK6LWnb7!D~^7enLiOFO5bds!`lOwC1S_2`fvi8h|XH?(<{;L@to?5M9k*x z3;Eo7vy)==$;M;p=zV%yq3*N4uXVo}?gja}46*+zz;rX6KJC*bvk&k0SgRks+_^!y zzmlLv7Q|&1Q9kwg@Cq&W3HlxEF6Prn9-fzUBOkRzB}1f7O?sSGl&<$hYj&uj~ZImc30YQ?w1OBK{p-NZn_2S)I&FA z59L|tq2qzp^3@wM--*mT)X%#HX9AUt#_EjH(bXIKopX`iRxr>f7D*-Z@2fmP=R5$c z%XFx$)&9R~b%FhY>tsLMu$MJM8*a(o)_(68j0^MwC?#ZQ2&v@PPY;L5U`+9Tt?T^3 z3b3tWE~w&`sN!24TE(Yq6;qMWp>%7#W3>c7q*>mcY`Y2#ex4T4joU^opa;s& z8msIDQ!}eiY5}lQDM&|O+pCb{Ip7Q1uHtL|i;n|~K|?48PL_W*1h`zxUBOBqtM+y^ zCSQ$2Xew_s1~p;B@MN09fMJ(2*X#?pk@D6myMY=2$P34YF-?xI$ki%@UIouj@2^YT zSr(0F7$APf;wt=(#al_7dYI!oy9sm0`^Th+kusFPD_3treS1;x{kUzSy;N=N*K0Od zR}&rmJ(L>$VTL2Q1`8MPPZg&Reg6#WmjH7$fl_yPQ9bCckG%y)ieS%!R|la=%)HuA z0mD!jANJ1V>k3vjyd$qMH0WHlp^-8jovZ>^7<8^*2mvsNysAayuxO2UG99mNFR$M* zrF-@byiQ&H%nqC$YKIO5>kTDkv=xZ6OZV8c?xfUYhdO}?gr{-z`+PW}pouRE|_^UB(&Mf!zP@9~^ zE`2qMTYQ)HM}(Uj$sllKz8b5K*;waF>It%J=JQ7s&EVhsK=RTzkQWois&^9$mrCB` zNdwi)vAO2>ccpaZVx2G|Da0-yB%%>c3+!*+u{c<4x@nx)fckCu0c{ks1&hITe7HD7 z&0vr^GBXZaAim7x4Q+BdgJVe}V1%m60Pt%Jj6?QU4M;jcNwZits)Jy{eair22aI@m zthxkdq5Vc(Y@M}*boAQlb@iPMF;7K#`t#uF=rl`g$3*F*C2u<4#e~PiIsTZayQuHh zjI_h!*I$<%-gLQEhCdJj;++HHlGIbItEn1=WsPn)U6W2J>Qbw0L2jMW!DQGps>Z7k z`(S2=LCw5t*pa%ND%Qk~E3SJ(t~j%24P)-Y2sMcraG=I|)NBx=eI8k@NO^`2@3t8^ ztaDCr&x`~y!M>SKE*GL<`%?|#hWou%!2BHs-^taZ?rE+2AZ{1yZ3TZZ9lt;UX$pJM z5Yo{ZmN*r~;Sn-{+%S=feh2q!8FG`fo}{Vhp-jh8sN@h!+mhM@0p9aP9)UqcyaORy zb0YbA=(ifarX|qzZob~S%;)jTM6!HFC3_=kh%qQ1CX365^`X4AzuOV%SV;wO(!9;Q z=Q33kWXWHP1V$th_UM+A_O`Y9anbNv1yC=>D*6hEv-Ui;twrRw+}VbY>6-truiz#$ zV;`E40rzEE2Uh3aaqFtir{C4;$i}&J^!t|B#2nA-AX4(arKOEN_t?s646nkKL4{_$ zT1zW@Mf>#t<{mQPfX~;Di818rT;ffv{0FvEgQ~%Cv~nb(_2XWeb}|-m1F9Ckho+;~ zXuGg?-ZNGdP+^q#^`Bn+dJV!?2UxJUH4y;S1`Ed?LFCo>`gdi4p0l~4N}(jwtR zr;PrHhZQKM$Xm;hyjYW`BV|&vlPQHYNG$qlZ_1tQq>B6SE0Jof)?h{k%x^igshY5g zcE_M8D<(N^@Cm1+!u-UDdiwf6OJKi2RSVB*?)Dos`r`Yd2BRZA|83LhEIaE1%_~?+@dBOPZ)pG~C6I z4tZ#v5LhFs9}DYvPwf7@a8GT)E3CJMtbXFAV#5oT5Sa8+O+u{S3{T%w%?|$@zg-QF zNN&x4gI`cV$?WuxMAlXF>iJspUmF4Z>6w0nWR~Y|F@2x%{Kin)IT_nM->aa%62h9v z(#bf~vU#}UwMpUV4$CCyyGuV>FEO6$C;p0w_et4`{T#m&>oEI@wjn$6TWiN?{a_yK zH<-fbQ4zCFx-a%YLaN-^9Q%TS3agL_Z9yE;z5aiQzs05h|!G*-`Bv%`p zscE2VWUmqS@>Z8`c|&2!`Ib(KV%z4-tJ{KUK*7G_ld&j%H^gCF-jH-tGGv?UOA|0QV*-KFagvLhAdD1|)6{__KQ=@JhnAB|A>0DNl> z8ou0`4ZdGlBg(Ge$X8%z4rD0k-UJ*E7(qox#Pj2AG`|%J9on`(H;6#_pIc zaGO!?OP`IL^9g^=JGX1)OzA6ien5H?=TB7X(q{?UH#6hJZnlIG(fBY;@I7FF8yB{BPyrw&6{bs&?*2t1U=h$jO_1Oo?tX(EW*HbvF~ z?R2)Llm`im*dQuMr|?_@zOSG!X`bn?liMrc_6kCf#W{q6SX;nPbGA=ggS+?|jSV?L zg&gP)hoY6fCU2hBXhBL5>)b9V=nB?YPSXhs{zJYKQ%Sxr1ms9J1(lGqd`yIj08%O* zGY3Ea6Ipv`p+=i%h!Qe&U>x zq{&DUxw$QLl5uR@430wKIiz{67E11P3fFJ}R~(ffqou&>#k<2lt7MX|Wiom(N?=o{ zFyh+9C>C&Ln2hI;l*6^pVK0Zu`nh9zw{{75@|s_Ng`)jiQg*ItBKH6m2U_ zjCD$wq=H^AJU;>72Plc;IsWg=^8mO#P-YaLv`_nlM)65t6e@XO6rX58N)hYifyqXA z{uJ6s!7pbRq>^+C0d*lk8)|5b!$xeP`5Q?U>=F=p{|T=GHNn|xYxow?V=Q7NtlX4x zlV1vr8RAX4{6v#*Bcb&M5%*T5M5V%%Cxg;E2d63r zBUWQ*j9l=qF|c&*M~$AcT5cr046(K>e_PBrFP@!2pd!sgm|$> z!dxh{DB#VeV$!`uBNNb2;J~cnbH<@Z;pf6w6ka5Qx-4~1yp-{7#OAc1HcfuSoQ^In zNfj6~rf>w1RLu11@;4;UVJjkv$nU};a2@gLG}49rl)_y`YBHUb34j5iGGOhR`Ds zOJJu(Nq%})zSBY&u8X7DEkMZuYejqw$3SzQ$(mpY;V8=EBnRYN6FYd6jbwF{c!Cx= zkZL-FIZ};>;MdB+zZ#<6IV0`fMZNE@e=!B_~VWoq#hT}!%3M^ybKEOf)_)5 zN)4e(#Q%(jy8FsSr7_0WIQqYzFW)Dl4MJcSbsWZ_#4t zkUo0XlsKwGIzo%d%gKFqz6Z)2i{(jtu^(h$9;k4BsY=oVNiZadI{w~;Br{82ra5{e z^Uff(9OW0xw|eOlCP8M9L(QVp9(hInO|**4m`kQ*>i#MwkzcOm^@hs36CwD}-kG62 z*#dbv8?d7dC9=8ldK4hsWgs8-K4yUoD8}is)v=wV4ula8%BMS!tTC|z z_w0*ru|F^`jw06iBML73k%hBYjADR@;RSCFn>_lxzj0Fl-Kx-WNlao7EnV!lMw7Tw zclo}0PiR=0={R&C%!Bpwd1N}FI`q-}tU>7c3mF)UhvS$vYh(2ae4P#L7oUM784F5{ zpeX)5!WzlB>Tt{lV`q5!rxY4(X@rNn#xLrEIjHi+YVd27*jO$3q%8n-NiVydVDj~v9yY-X&uX;R`FNA#qyE)T+?4N z{;b9{nXfQpT$cp}*t>M1(??s8>Os%_DUK5deIIUrrMG^C%NK1fpC($%EvELc@4^zN z;^?em5qvY5yPnA~WLZz5X5V~SG&{`18;~gb2kT({KE-27L4(TOVkzp`KWGd~UmFKg zMy@S(Q4AVe`YN!eF@lKgCh;R-$5%7=txj%GN^aVNEFf@B6wet;Mt{WiMT$c3HWj=F zAzd|Yu`c^U*uMr{48KPCj_3^55PkY>xg7)tv8VD#?CWQX&hj#OOh<3G#P$W2{w8I~ zyZw1qHnd$RT-v0dw1f4wLTN9arKRo2nxvzfSYq}?erKhrJMpUBvsY{tlG7=n2KJ4T zbXWff(~qrt3LGu03SX*pAd@&+`W%i;O*P(M|2$G@>F96&;VW#u-d3o>R;CJiW)b;@ z{0ClyFD+ts>VWJ{6|?>kW>_e@FOfW)Eo%;}VZO#{zXAsZ707=L~c4HY$zb7Tdm~8pEMyijwo&`bFEiaU0iyEh+yOZ5s632eIMV z2ydq_+DiiVomOCvNtr%{47Y}<(up1W`qBW!-wmtDLKsv1c|6Efn8 z0_{)WbgXh)bF-jwKN?zB*S??yF5)JPFVZGxqb3%D_n$TwUs0qQWvjv2Y{TQ3j_W2$ z*Bj9FVf3t#CPnfIn!H1htZZQXsb+>nXhE9lTDZT^Cn^j;PJhO)HeQ#qW_0j#l@lHO zCvTaSgS$g9&Z_*aXNZ8g{ZxXdEU`HFz(~IE7r)NA{Yfh6+$T|k>gDz&gzEu*lIdX9 z6VvGFHws(^_U?F^CWi;OdNbD9Pm;_O(H!f6;YkHzoyO`d+}dcLIzD&0h|tR^2Z*09 zO0CgJxpCmd4FrKg@b!nA)9~A;Ub2!!5;CGiD zUZL#z*;4ZG1`6{SNdreJJncNsGDw&q`86H(N+>B9EuSFh^GuMZd?ITgIf8_O$ycXKO z97YY3!EQqgkZ1i>e z*h^tOG?MgLAr)?Z8;Wj_X4;S?mA4_vp;`9+xvZR^4Nog8N80cOlAzlL@J1a* zA~BFiwQ!j~eR#q91(yDT6{AK_514u29dAk-a2|Mx-g>B5fh-WGz>i{WCC_&J$+xA- z1qRU|J~l+WsX|pD$WVRBLX2rUJcy|qAHC<{Xaj+oWV}pAcUN+bldEuA1GGkS5iOSo zO%qg@{NGqIhYd6Ldr_aE3c$2-IbI_FQaPMt&qbd8wa=Kr^Ywrr_5hqw?2){Z$cX zOyS=|alhjm5;rn!{8L!}^{W4}!+fCF=0P)G_DtkCh|o+|&9DMzHu&%*4$^CI;XHBV zqFQWG6uuxMOL%l3X4<``y=_gTb$etJpEBB%+b-wn+T1*SNx{OPIK$=B_O?$Wt(?3o zjIJ;mC%&l=%FTRAwDDP=YD|gWT@x72Q4Y!jFHBpD-0A|c`4;12_Pw%#KW=aP_%jsz zF$#vRKJJQt(%$w-q;*%H4<&_-@eh-7_ES;cEqG1lyzaOLU3NwMqY)x})ZX^dXAt2d zsXHjk>V6JZwA3ZQ^NDN8+hvQLFUGSP45}QK$j2tXo1lW{GW>AM+`i$5V#z-DO@I_!63;n;9x@+IB}0vnei$^ z>Fd(jJXogehehW{BeUbl5mSHSyER>p0t+@j1m~QgsoIL_Z{VWipfYNL&-+?XbPxPi z7XD!NtJ1|8S-w^;{e9SfXbEuH^(edY{>5SK&$!T%xgOL@P}V;_WXF^FPLhH z#1cjId)LpC;(R{~;$ELqw{ps(W#C0%g{%zwQm+XsFIIv9LolH0fM`jyCPoy%1Jl{= zPQ3>tW=LWxZH-Z0KKwwo1^YFoK2|)!Ch&%6ndmd(e86r1zvmgxeJ3j4VA0E`(KV7V zJAUS|C^v5dZ{N*v6vlFGl0p5tpO#&f{|>f)5k@^EAgRGm_SsBpHbbVXB@-%Z4p+DX1>}(<~f$aKdn^L}1r~B)8 zxB+C51uzeD`aO)EKc1CAQX`XEr<#T{$KvbZ=uQ)MQ)NLD9*h{WXx{I1V{#*+q(m?q z1e@v?`En1sv4v|}l#d&EX`A3n66n0&8%HG}qWlcPWy*#7Q4NIaK(SCLTZIF@S@~rH zL4uLv>mq(OY3yEp%Yvp4D-M}{SGnmouoQkZiW0gintpq^Xk(dacN7IV+G+ZAa%0EP z!1NE_6@+6**zqux8@UAGfj03-;t3>{>5%=thTKi7s5QR*k!TYx1ic#DCu~&{w4vB< zq{`vi>dZFO*Vp%s#;(?(xsYN$^Qi24KT@KOFM;+bODoooQHQ$~o=0EEL1p@c@lH<9 zJv#@MeEH5^A)PbFUR9)zEcW3)87nYtpKVhplXec#khB|kH)2tyD8Ff$TFlf+Eo2T4 z4;%OC%zA`0lKo|vKDYQX(=fj#Qyp@QTa(9H@knyAdBx(Vh~(koU)dO0)u)<`5f})> z2hvzZ^MMnv*jv1oh4HPHoRif%imUv`&S;d`zCnRoC4*W{$|K=hdcZV-14y)gb8u|! zgAnk3{rW+yY%PiRps}*R@z1tjmhH#bEO2Ue0gIuAtT07DLw}IPOB}DQ7MV3cP4)=5 z#Ns#D_5pPPw*}-HG`R+2Kj7v-rNE@!g!=h#biGzKj_-^d3cJ2%)Gg$#7nl}@aJ$C` z!^Xw|Gu+gBZ3ONAlzQ@e9|j;PZB8|=gNOBTbp2f|NualFg02XGI2g$eW6c2#!2A*C zfH=a;sO&JVJh&>`Hc9Su_Y?9mNafq9d;(`aROPW=j1S&1anDsRv4#EeA#@D<&{WmX zgItHO@nG4}?wBx2X^`!yf3hElJ0E4-->Qy5l12$Yw%V_fb+VAGBiTp^~7WaN|c zqiwTci!@8qU7NP~ftrw99>`Kl%pnX9T zGM{)5AL|w0M8=GOQnDR2C7>J;kM)!fO0yAn7tNRW=DMuZBm z7lW-&S_9!mBdr-pT3dm%R!LDX4W$%Pp<`_s654>f+FQ0h+IfieL_*vvt}g4H5TK1~ ziuN=YF5!#%9>GM}?ZmQ*!_sN_!{ml*AFc)CMGGUn=qVvVowgnb3%Y@~6$CB5s%ln7 zsHru6vsN&`r53Iga8?~G#(WJOVGudlLdYs=$01g}6E!hezxTAR9}OxU!*ubam1WF_ zWvupEF(=IoO+pjpg@ptjC{g^X+ra#6f(?(BI}9yyJnrLlJT%GCYjU_NelI-RNzeEm za#tKO0KHc5kfk_k3Xf>Mw)-d_5SumotAqS@qSEKDzW>)F;vHZ~blGS~u&Sr>($U{o zR^mj11^a{L=XK!alViM39tc}T3k-;FV}79}avk7YWikN1IW`L1d!F3skNUlXRruU` z@Lys!pjw~4O!fdRx{rbYs#f=wGZk7A@YI0;;ecPEnhh<#RwUORV$eSY1MS2cqb+WA~5Sg|C0HC0}D}`7KYbHo&Y=JG0ubzL!Bm5A6en z!w_r!26BU!g_p1Iar5Xs-@k!J_6S!)u1TUiBf@v0T;nn+)EnuS?vP{pJ zh_}Gi_*a`qz6uH8t2hwpW*KqfhoS1Ly)77Gb}X+GA=6D0gL2e3d%>EWz?3v_iqq7U8Cmbjon2u&uhA24UIsth|UK{Vy``;-* zzt>Bxct5y1&`oN>88d0=(H35GDYqXz*sjc0dTBmG%cYxs@{dN3CUS&t&auMrEF0ps zNw`M}=HgVsk1cWN#dwm-j9t_n=#prR@D3J@_Ywk z4@0^NvohxXO(+c_?fVL{tm9>MFUgV3j1$*ek!`PV2C~!pgk3>eNe9^8-;xEsJN|=F z4d+mym(`OVK^CqFCFN!H-6Efm@C`H^7pJ|dGR-M3pG8l|BYL`5@>q?)OgKvuja>Q? zdiJ|)!{QMC>;0%VXzz~Y?QkUBa3mR6R~KaLvDHgQ$Emm#y1}3EH3r1WtyF2@d~mXW z?s@3s?*TCF^&n05p~KnjO#BVOQJx$D$$bGL>W#3+;ymP)0dry~vu>Ceqf5{rmyY!5 z=vePYVs|VOH1(h?~a5Zgz(}3S+ zJ#dF*a@-4om@_dxBP-SK;aVUIvAT5hfqs)!IjILIn+GM>6jr z%fj$j=M&n9wlk1f&>5`L(8C1egC4v!tAz^Ng)O6mr@Wg7#XfrX5berB929IQ#14=C zt5WPK6w1dKQIjcuuTT=~=pE)*hYyjX(gGiHLZGw)l?Lh1z=0iYeXp=Xv$D}8NblX7 zC8nbnJZALGEd9(Z3f>2){)^xd3J!hOK?M(n6yQ`)KQ1jO1Qu7KkmZJ{D%cAeF zX?2XqpQ)O$8{vLob2Dh^$RSXW9-g00GMs}QB=^#$;B>zNRpifMvh1;osPh?y+gSSy zpx}(&)`^TXK#ccyLLA!v0dkq_J_e`6PPW6H;hHwW`Lb$X*G3)0?IVv$E&W$`=ueoA zpg-m5uUZ;#kAVj-m?-tNmYB;$DGB`M?aB17s3Zy^-!t@cz-2y1;{L`OkVv!(xZaZc z1|?^spq8ZMBsb!nb4MWg!c{ruD1Q}{j$SEB)b;mt^s!}9lP;9o4r+Ra45ilo1fhQT zRjEw-WZDYOe=+nD7?$na-tf25ta)%6ASZI%fmfXCtnJnXQK2Ad()JyhWM1xYC;rtB znLKhT27U*~3MSDhs~B(Ru^3XY*$_V9Z|P1{kB*}VeL1C0xkr0N`>rZix3)}|o%fGe zvNNmr)&4rl2HvHJfQzl;MEDtueo>#|zPO=rwafV%=>J$AL;|&nPuf%$dfY~5f_YL+ z3DjM08ym5h(K?a2EGCC*Siku|SJbaZUgi4r0{xyw zrs?S8IU}Nuf;r|n-O+EZf_?+s(eLEdV!>9w4Mr}jU##<$FH(*6{sRliMg1Vsq++qk z*KdmoAM=G*C3fU$m+%ZL_P!N)k)hA(sGYh>NAC)nq+pX4;>I^*d{b74rC%jAR54Wx z`uQWimI}};2hjh+-IstzU0rX7WlThIqC$&SC8AW2s<=a4h|9FmN{tFiwF)lauCyh% z1_r`t#&KX=(730ywW+mrs#W8LBVs06tGq0ixREC_rB-c`&(uK{CD|( z-^cTinYqik=bm%!x#ymH?!D17kmZ*)TqfuqETFR$Mb2bxDkL*5nRZs$YV=bjRTv~z zE6r|f->0N(B0|Z~ho;Atl(|BesoC-Ii|R}C?>;Z6*!`efeoHH==QAT2T87XAimLQz zoW={Z5#I}#-;UD!H&>2FNH(%Al&)m;yo4cUtNDyZ@da=Xwk)zPInTI_&rN4U(r<^3pN)5`^yjJ}4%GzCkg+{7F|<+5+Ud#E*VGEFFvQ zScBnmEbd%Ib@*5!d#SGuq?;0PA^PE5g`3R1HFB+)n(kMkacbICv7ipffwENSzNzWi zy@l9zYFhJzpzMIyAlL?`rUO-R{wq_{3wL{%n>Q_!p(6!xdojg0K}V-1J-=>x5CzuW z#S|6n1Wp1knSyLmX6;YOVVSaSP+olWI6G!0Ih3@@Vf;&O$vdPZ^9+=%j5vWJmj{`% z#@E6AF~E6=kf$%;zri|5%Ky}62&|ACBLZHJLmi6MA%%EBDm;ccJwmtODEx%@NY=gh z4u*-h0=&q8D%!II0u?a|uzmr9us*&LM|7ozS-gR@g3^{FWo$ln;$-~?H&)#mT&wzO z-P4~>5En{s-Doc=c|Qfq^-Aw!)og2g7w!&T>WXci%WsPvFAPGrhiDr-io$=x>4RT% z6)W2q8b$4QT6~}u#|u&6v+AHLh+q_@@y?m6YuxrsuO9HXJf1O36r5eKPn<$#z6hWp39(&9FfU@ z<1yxI#5f+ew@E?AW#J7fQ&I)U8zSj(SG4gcs}zt|N*WS2n6^-E&z;aK~3Y_dG~tv0VgRS2EVe zl!hH4=brz5w6L0!D$3E#P8)^a;V|LAhdjzp^>`&ALi(G@X$d)#v(LR{&OZO>HuT#7 zz-|9?4zNE6d-PJA9qy+o_R5zE6Wd{=ekxr^J%^Imz)5gRGYAAb)*VpKBbmW_J`5Bf zEqmFrLf!>+^E9pV58{sj!OIlW;f1YkN7&E6o^2QS1g?u5q#G~6_~=Gqb{k@wb&h9` zBsK>zHuE!NCpD;fn_2E|Age=*L`=qovbs0DiRB?_o1 zGn^G8b;xzyt%C;~$GwK31Nk8iLm~L~w*b1lmIDFzk_Slg;J53gPL3a5=86`n(}kvC zsYY6jr}QOB9{jl0c_LWn0j-k}rh0X5lVpIPLg%7OB3wjzFgweJ3~$g8Zo^A7O1^MK zP<-%(FJPli);U4;Ga*B!0;pED0t77UjL-@^(;+Q%Z=gaqD}2m(o$GB>;C%;9nF?Q* zJXFY_g8!x~S2E3sPK~|a|H1xb80V%teA*2^W`cUayTM3h_yX#$l z4{%itmzl|OD8H4K?`O*+NwSimZ`vdYSmK-4rQSunhZe~gKr%X0qV{@?TB>;|5b_NZ(o=SG_poSX;$Q6T3apXk^IcLi^7A^0ZeIg-f9`D}hm?tg+{ z1LN-vDz3` zONL@Um#&g4P#2ucA{lCwd=ff@X55A~Tj2wjvS*lezPwDxVuo!%H_5_=QBx5Azi6>m zG}uPo_#SeUqX=q6x9a4zEo;P&0M@RHfAH1Eh#qR@hb-qrcb#@4h027(dCA|E7l*C+)TZ=Ag0_L`(ff zE2t7R%;*`A>bU|aKubb>srq{XV+}tyT{%rsz@nUZP!KFHi3tx1LJYFbE~ucBs;8D} zAcB3RPa~P*OdMkBjGfcz6W*1kW&_j#hsw|$zDL%DcI1cl=+xEz^3`mKAycb?k9THz zO}lg(Ls{l{d}6IKadq=C{i|4xeNxK?SX66@Td?EE{#6yhz5o*v0tSuF`@J$s3~`LT z_m5hUxn3>G;3$Ep)i`7l+h|+(=Cxk>pxEN;{Cy-%9AmsHIPAAAX4spz@Y(3$Kibt^ zarbcP*I_*)$9C6vWa3YrL#w(-El6@jI4(PCV#I(DQEU&gcmtPvnIWn${@kj%F_m8c zhwMf*gz>_u2WU+&T)HLbgsXTPw&%X|@)~>T2%_pv{xAY0k|*mdVEBS(z$~p3CGZSC zJPYCnDGG|K1X_(y9xlld9p`3vp*@bz@PY>hNaJP3XgGGg&By2nEd#a)F98XfgnFCL zol=+3em3=OJ{#wOT0g2yW% zvXD4H)Kdvmfdno$v~H`>T{7?_LK^|Q!@+UrT6+qmU^fA3*cb9T$a(1vwBBD^M-~RM z7Za5sX?^b-a(08wauD|b{z&tfhKvM#Ww5&Gy>bV(3e`cK+G{M9SRdg3Ol|2-N!L}B zJShA2ra(A?5Ohxi&>;i^Ko;wmc6d2dAP_wo?B6QJ2aJ&RF<*iki0+j+00VRBTtCLB zC{+Iw9FlqJFcc)lQH+4j@War1Na!^ePc`>1i-9^+14KTLNZcVHH5Yh5u@Vpc9Yn=I z5NslJk4{&C&N@~9N78C|kfjH){8=oaQ<%x6-sp?}`rSs@tK_pQ{lI0!Rq}3gy@U)`JSF$n&dQ7u8 zH|nGc&65v<>iK7@)Gq+sFs5YSPPFJ1w=kukg+b2tXdxd35HzB>fkt}n%2xM z3d)2R`i|hCaKWgF)fct#a5vi6LEB)i;)f@d0`gT?RBsOJjg|oyW3H)JK(*SUdf#Ea zkA<%>&4pd&z4_h=h-7Iey<~Hz$~OjzN^~$I_H$y2MRE`nOUlk7W{oXs_NCX*?EaNP zO3jUQ_T0>|pu)u{6HmAv!ERyCv67ixAW{Xa?8!#fDCp+6g7zw%sc4TU3}TvvN4Pf2 zKMcPo{BK!~f}DKACW^=rHHyE1dy<2K;J((5y&0zc;n*N!rztg0o^33LGE? zC56ifPNJ5P=i!CFp;#A)$z#7J&jvqzh=|Ej`S9~}WQ_SG8@r5+J!fq#w=uX)0#Y*v z1`lu%pssWtq0OGB&7NP>>~9yK?+o4&!@!6-r+E2l;3tf#8WV{Iw0Lg;DF-?2uK?AZ z1l1UG#rS<}JJMtHQfvJtV~$jWonQ_GY~jpB#Z-Q|>VIbIsfQS@aE8M^u2x4j!d+tO zCLB(cq`9Zd53?I>`d-5_5ZFCG_T-?oNDgvE$p738Y77NMM2i7ZJvBD@B;T+aBc^(xe4mK%VCB25s9)U${i<|!LBB-4 zH7n94^4)WJk+6L2S0bYm4-y&uzI^y)bl!Xs7TY#dZGom9MpYKDh<@2++SaT{d;W*O z&#sKkiC5WWt$q=8vClge9q(JRqv2swo1k>>ctPpc@&T(zy&NCU1MoU@0~JP?3dd`O zxK_9(SRsQ7USr;Ml;=V(yUX>am1AMRLSHvh5KS?0iAOsZY#vf!Qz_knur)L(%XWrzAhwxiK}Imzbg%D4z! z8@q51@T+fIotl4Dhu~a6mKs-!2K^Nj;y` z#eFw;@pxfE=Kc|nL|MW15e znXKqjW<_sh-PJTjn)*I#*cT(Ob;;Hg0!Z;31ZH9Gd=?Wf zOso!mc+RGk*o(`5BL1*Vz+w+#(QlKmxZPlZSzfT{Ly#4)I99Q!4`MNcSX>}j=qZYf zI}|qotDE0P?eoP(DoY#KOCS%lg=;eXnurxZhwBThR$q#x{CGg`xf1S(a%2yJR$o%9 zj_?le0w`i`pR5XGoIF-&L48{ST#utJ3-7ldd@{7clD!`RqltNsSD^U7$F|5j> zD^fx`i-?J!h*bbVnfxGLZMngiyj;R3k^Mi z$XZPP@KeF$c*W#|ASUC1i5OiGarZ(}(&&lUHA?F-!3&;>+%bckW1;u*L_zA8Ayif{ zaz%nMtw1maId^sf;bv`CbFt>WjT4q5ecuGe{Af8qL?|Lffs7?WIP^ zwt&9DQ4$0l7$wUfGL{&al?zt$QwoSt>0FmJqeQb1h0RgYb3sssuD(lTXs3Ndnuqhl zljct`_H2`Vg-vpc&p#@6W_#S8KsuaBbcd(#s}*Xv%{2#P($u-+P6Y9zWDc_nO^8vf?(DLK2J&2*Z2^+ku?Y(gRFuL_T51cgYQ@PX?}R<&tPdg+uGSzDTmRA%~OyH z1nB}ryTt^QszQ)ffjhBF=0p$Tkizya)%GW7`?bOLSIj1Ip=-Di0}4XG2hWkc8Hq%3 zlr8ID?im7Z`IDS;brCCDli(mtP4u?N8z6KC7`z4KI|gHKtVx9WnTEH)1pVoE$pM#_9Kd&}S9`31 z#M-xoLlxg4GqHhTo>@ov1<+?+VGMa)EIF$==V6%xrea+a`Ya-{7+!EUdr`1r0@Yxi zWSt+uSA#ml9g;VADeD9Tbp6Y&j2O+ZQ3+k~OTdJsEhqI|6I%Z+Q1aU{N>%bAFsFEF$-IGEX>bO+1d@ZBnj#7fE2Lc*ruoCtZ zTS=o%$?0|tN=(9+T?E);_JqJ!(Xd`6oOUh6hL@9GUT|2#SYe!9({%t;0Jo7+lo z$ybZlr0W#sn3BAn6S$tLNnNLzPxIUQG1_?(YCZbnywiqOtQ)1t0hW z%mngnP=~EhDJ?)^pmu`&dEzS59}|NS3}6dsCo=DocD6Hg!X+voQoCF(jdQqJ%=(&~ zP*ZM#7|^(f^-u3&Me9C9z9*r1$nvUO?vZC`?E+swt;PrJR7wr-t!8GvAd`3K`tv_< z^Ti`OL5`MupPI@-=5DESaS#d^m+N&_x|wXd6&=$a5cw~-98cCm3Gp@p!RA9P z&T`24LaM~8ov9c5)qj1wktZGQyT%oj6Jr|^mAe?60Iu`a`9$Q2HQl>Jo&cuHX#SC( z{Qw;;dHkFv!e}pgNrvjYmR2sbJ-;r#CaM#e#Zt=67MRaA?N06+!wN(uDf6455prb)dh zflboVfWOc^ls&c5Z-c+?-MrGYZ zKQ+*lhpdvJv@Hx$9VV;U!`jJ|N1<#h9)oJApdP?Sh12-OI*}FDrdx~#+j^fH6zDyE zK}q!VNc|`e#e3&(NP_;sRSX(AlGMTb-?lKdDQMUq=!wiu47TRRdqJjA;32NhvE3(Y zaU29vrPvIB-%@}rXd}g*K_9iUB`(vlPJ_0dx40(x@@iY6KczLAy~XK&6ob6mmK>mwCqjlSs8-ibrlY-sqI3P$23rKsTTkLe4zRQF>vm z%f1SiP@Q$S*NF|zms)I`15a>nvPm3ZB&~EVrm5&QyPRR&t0c+Bw6uFI zrj;FQXu3NuuraXwWSSj#>{z*@aa@k%pGA8D}6Px(KdjrsKsd( zb0|%KZq6cR_C@y@HrL;sGXJS-I3~1~AtfjdE`r4%wK+HhC)rzR5844AXmMR+fXiBb0ViC#%Bb)u zd>It8?Nw{lv{p5J)NTZT93{JGROXZ_E=;B6tyx_hF!d&78wF@~Ckt`u(`}OQ(F&ygUf#*VCk1$`C%)(YG*keU-n7_7tvmX9xSTL3W@{qlj-p&Pw zNJU#f4D?4hGjJzE3+^HXiGS@dA)O}g>Lu6sl1JKP@`lmET6(uYvheEpS<+04DKT10 zw4h~6j@_AL!c#!S6t(jB^QBgEN91Zt#ya=??q+l3t`oCkbL48M+pAR@k2H;ZOB+dX zX3zDj+k)DF1|C3HdxE*AlrjPmO^pH;SdYP79!R3T70nuPdbS427~a)nEuBoXHAQIl z(gs0ctgqFjzl5`u*)i8bduw|-U74+Rsl5*}IwJ3Ln4Kd!hVYHH#Qr*USX={Y4QiGW zni!Y!hn$O>BkzM4?<+BI1V>>cYO64|9N8j!U#BNi#L{a&7kxu7bb|EiBfV-YpAHC? za!VK*jMMI^PN7t!NK)tKvw*&*f{Wj%+a1xhASF$i`Im3VPWZ#efCT!>!`HL}^$lET z0FmeYy`@9fC4!4wMS*OQ!_ToB;0yvXo=>G7Hg6}^Fd3|2P;6ovDEv~iKX{%Dt-egK z;d&xR(TCm83(lA;at={!a@*-ll&RBT8O)&*1*4}|gapBUALDsk(Sh+vofs#PoLmq_ zt(;)1FN?*>b07s)D`fFN7XOAeu(9IV+8QF6pNBsb(_EF-Yp)B*5$@!W81uo3PI&FH z(x6?QHpIF0A|Y*VN7g~z&9$z!=~=W`obz6_oE}{v%d8P?seBv&$q0+&c2XD_D3ls$ z85M_nX`*DD%g*pKWF{EW0*1grAKHJ8P`?dd&%ac@wxem$>R?1h7|}{q@q6qFG3Tc$ zo+-#uj=z7#-_}Qr-*@ZpE`v+@=lq?711dQY+(sQ0qaBp5>3jUyvRV@Zfz;Oariq_`aM38zyoV3K{R-WI1Xcl^9H#I&yw@$QVeUncXAv@|=SUhyv9t$r z_@}%=%#svo?V4v_u8subT+yF_aTH2FYXZUm>9V`Oilx%LL+IHPcLb;O~R3LB_2RqkN zm$0*Bj%No1w4^xyoQjfQpzGD3MxYC0*%cyj)JY#T$k%S5BBBG~%K>f@rV_9@5J5%E z`Lnjw?pj&9!vhKqvj<(c=bec3;difdwYJ&own?FH^LbKE)V7JnkG1Vi0LhRnXd5`7 z?KInde5JIV!4?I5yB`?JIoZUK|#4%7G-~6%euB%BzLlxRzsFKd!Fhi z#xz%|lA#egkx`;*eqsb0^kuD}M4kJAN)*CcD}YX`5#UTMe?yui3KlgF)8CoHwH!A_ zfp!IolggPRe92}*rJCtSO1jL-V4>)-p_rAK6=4=v#0{MDGT05_Pe-4r&tRtu_{b)y zt#~gg8UZ^Bxjp-l;29h?u22D;ad*GY`^pIeeul`3wlA&2Ja!`s~JzB9WqbF`~l*YI<%0&m%Yz1UX&3(mK z1^nX;y*h(yAA~RXcIErnqIDt*K2Zb}Zt~2K?D;=Fy|z>2$U^SqPakO4mk% zJvjwZ0M`*$_HZ55@go@;1p~9r7~IhR7t1fP+VyrI_73AF3Ix3yo%Tea2JkbYAcpq^ zq;X0fV>%%ELXefoDk3;EgYI0U-U>9;hTdBVQ^~*ZDc$0T;qV{00 zvuk{py23C9J$Kho10_Z*4D)1=3T)hMu46nL`+|0$w%9Bfo7>&8&QJP;9u}W#B$w#b zLgBlW5+*rYJI?uK%)JV~@(mVIWEm(j;ws__m6jiXah)=ie$M*I(6twMBaD>{ zQKp(wzm!%Y86^b;a6i-iCPJ5KWI7j_bT)Dh72PkKXk%58+5I519*gq}0L)_XYg5QG zpQP?@{mHV61R}WfzMr{S6IdonGwVDzS&(Q)i|sIudikKQfcl+_#yA9oD)QDok^=2+ zfw9;ffuzNk#ydy5a%|;oi8LxT{+kjs3DFoH)A@3x($q-M94kT3Xbz^$Lb+vmBSH1> zK6=19rHWBTV$-qr>dO+DS3@s5=wy!3n*nznZM2g^ANKO zC+VDBDS^Spc2ixKM$I&;<$CtCGWj<}uAuy@y&_<3D36Idk~4U`#lnGMI3h5jaxl-v z>M%42!Zs2qdWSWWp)Jn!55OD{#Zm=PT3W~vTnc!ELRjyK&(X&x=icLiq2FMsmj#t> z2>taUHbbvL=$3QjYd2V0Vo~+(+kk?cUvpkFP99@Nib@-&bn9~tDr~L>&2jQ~BsM@a zM&NPr8PucH4}KioN}O>YEL8&3L+}l(4)>J8L~tGQ)LrV70**>pLx93;PAM$%0R{?C zbNtVZnQDIRsD)<$roP}T!$pi5GQ(S**2WuOa8@^(J{a1e7A34g!#a-EX1d_gBF6a4 zI%{y|qX};^NThYwlHn-Ks+ka|=`R5L0uK z);vXP{xDc`DQj+HYHn80=Jr~1Z>_mcux2xBvM+42%GBK6w3*|oJ~6mbYgPqoexEh( zG&Kj}a0@!J{4p zf};uz!9b7-hiHYLYlU9~EBqQ2z|1Z%8$6eIm`Q&_k};DKkr(7(2>3GVP1$(u%|;kW zfX(k0kT~^?b%-8YDYda$93@|91DB2=L=#!($oXv27j+akd}C!4S1yUKqJ!&$3gI$aO2rN~B}3eNBhAx5yOB(D88_Ed1yZ4l38BnAe&?7D+3*s{S(n&qDg{?o0f zv`8vp7;oUI;B6j&C~oAb;E#TXGUc8MzMoMFx`^V+8N}p&Zv9d<dfKsfSHsjN?o&pcxVz#WP36QLFw!-fsgk1n38+ic~0hXb)+bL=P@)|Yy3oI~_O(a5p zH#vz{*R6NXMvx&6z%HA4%3(h|^_j_a@%O;xvEVW_VoqRL;>%2ubxu;V6aogRqHBbT zkG9Di-mC;EvPK5J$_@bxY#yGJyE3s4pyF+<4ErQY?@{r-W8xjh#9sp0M8HJQ^ywe> zg_iG37wS;t4mramR4dU8iriMOipVJmEeAn@`IuDdG>%2}X-u6K>;tl!BpZ=BU9YoPLmDO8|AVtu1)RM3rHVMP`FG7)oE6+zt34_-??zY91c&}to3(y;J!h>N zR0U%JqF~nIax8vKSCyTnn&jF$h=yl_VeRE!3GTa0c|JE6#3u7`ekLg2g32(YL;X%S z3IyxJQ{`(r7>#L5bV&y6PDWZeZkE0bNLB<;g5Dg|Kg(3ITA&3wt~xNLyMkl-N7s2{ z8d;)8(lG)9P~*{h?S~#Fl7%>Wo5cN!vQHad*#_YEDKMQY6O*ZHdyOr+#BX!~Q6UZ5 zy~eaJPk~vDkHO+DJ*Q)dC(>*SlS{@*K+?6r`m!`cvtIp?TAvz63v&<|)56Ig4&eTw zSuaBayA4x?4qUuX(*6^GsB(Ao`LR<6bIytcTpg**YgPhYAfomC(P$pOCNzv7uMI#wlIDO$oH)?L52n@{ohLya5D?r%`J zo(Cw%K-vwxP`O&O$9$Cmi%IgG>0k7(5P<|9H0LLyP zf_8kDKu!aF+CZOHeZSlkNyhk?Acex17l+NL64$V-duofPkAMBR+UwU}{ z;Uw@(kQ4UaR|eC_WTbPJc{7=+k<|U!6(W$JkIYw)mrZ_nemB$s7r}n1E6jteA+Q{< zG3E1t`B(f$bLOqArUb{Itde!@LlyF|xR1dT$_(wfg;#+ ztbDCY#;Tcx9=!Mhi?|z1EP4jpN1W0r9GWqOVYmDR)|n{qNw_+6GFtg0yfvg*+c=9A zt!{CRF{v_Lvy7c4Ni#I56CbE0NoQ+P#+P)CCbdH{Y^ifK38shJyYrCL5xL)%xKMuH z#nFbF3uB~|Jc@l6_LT0@Ft)mm$eqZSfJh;z9Lzs>jD)k@8Ew2%uGJ{a-b1sYEdc4x z=Ez<8&A|Mj>BdEh4R(j6R~HlCqL%h(*xuyNL_W#6EeI~;1W=RpBUTkraFb>U@H@0` zO_}~MVWoeJ62Ajj-Qo7{_7bgrUpjDmwDIP7v~zXyYvv;TUDpU_T(({{Upiy2ay7=!t|?t(>a0o`EIpxiU|+$$Ouwa{QsEXu z1!}k%;Z+$(n=W>gS{8R5MU*LibBB?;TJF)A5~=%olRL8S^giR`AHXHpI4E&~|z zb&B^@QrY>Y%mA5Oo3k}(wBRseHArTHB|maRx-sKQM@D4Vi_112`v~2Es8%z z`pqa)IFyeP`MWvVcyq~-aIR)6-EMAV=-#ekRemDS@65Q2*2xr-}V0@e%D8*UvKbJ zP~q5<9`)k<+Jt*f3?{O)5x+8SNOrGp=8ks(A=r<8ygi9s=`^*)+T#>!esiJI5xK6Y zU)P~u*J;0CAQwPBc&ZGs>vV{jVwE~`Kak4*8RoC0+aiIVDuPdoB2^ato&uldpj#{) z8$^3hRd-5h;`Tx=WR#0V7yX43mcx)7}{EZeG5bfLCtig^@0fGZQ^nI>DLi6ol27NXIZUN0zLiw3UM z2J}FkZx~&xfUj+iBvC4uL7O#r{d zzldKVIz3_VQ&8a;O>slL+xfLA(rp=dhyHdMDqh3?@c_F^p}C^jir*cPYl`}L4f=V_ zM#SM7<(etxiNi7uFi#xrmL}pPd=BIk$26{-qGcCHD{*b*4AcFeOX+3`4o+h7#<%8Z zV{=8rIFtVi$;W{q%WB{rKr>%`MvkVM zY2bc%XRTznN#j>3wct#)Rw~W?tI+mUWhG_iqG}Z89_#lrTzeDkk$O7euZ8(7+xr-aLk9@1KK-yW;S7(;~a;nPVW8_BJx!-KPP`L{b(xcM-%$dr2Qy6 z%{3_-O)+m2@}d>rD12BDhQkf}*j?B<$-tX%j<;riM6xyIX=g%n>})Tu-Io_%TDRu) zy6{ohB+zIfW!B>?GEYKn-^V-{UibtPG*7;AyS-rtlOpDW+Hz*$Q_(XGbE!0{-Q}q@ zirZ6w16y#SqXmBeJHoXAd%k7G?`bU+I`MlOwHTLri1{Q;QmN|cXnV5KldujJ_bINp z#`P+4bjWr@=7W__)`MJO(P{Iejq@uS_Unkuv+326J`cQ{r=8=i`spf7RqoAej?C9@ z^QA~QhG=g~z~_iVlMwf7;YB-mONHl=5un<+hH8HhtYKB*m4hvWa&_U|Gdy$P>&rm@0tvz`b(qO-a=h+4_|c{+2)pxHNM}>WIw+ zOoeig)S=T*62TAVDR*`%o~*L%q@g*b8e$?E;)gbLadAUsCbQj( zOO7cu{qIpQ{YzufU$99PEwA-(j6P$-%y;=U5rLatP8K)rYtsKN=>@hxT{1~_0wp{^ z_Iz`HOgpWFZt5&&9iL6{9)5Sk8cR`xiJ%BSlp>Mhe(qVSpHmC_Nf6Or@UQ8o2h*Av zD=c1l95%%*n5)U-b>6c$bC|w{rWZ`Noj+LYwK`09cwC!S{yz z)9=R{sJXF6mHY_x4*A87DU@Z*JVsTozK*LGy(ZZn~0yS z=94P}4$2m)%w?Mk{G0^q`1d};G4tAVipB2GVz42xgC8vRSL#xRMM)$`*7>m(&7df^ zx4hldaIEYrsXMcc;eQfTT2ZkLk+o~z#5Pi5=sJERBS@7v;MKd3T<%?PHu+6S9{g9W z^LnsOM(cDz2QUWN*7=pzc}VL#9ISJ#)&ZyFzEWGKL6QMNBIFn*&uu;&mgg?A26_GB zT4IQwCM*{D73IQWQ77x{agtQv47_P~pniFxRM_-~2>+lJ9JZhr{gp<^eI@l)R4DsO zYM*&H+(aalCRQk^{dS zL^lpYbYl(CG(e}j5PYwN)=MH^3mwmI$^DNIY_iS;xa%Y396bVm-LRor+#*&GrG^2+ zYcjbBK9T5BZ*nXO1@M_Oza9D17#H{&xeJf5s;sjGq8C6Ji@z0N{Fc$M0=L+pTUJVK z@=KCDxaW1CoEW;29}U~mT2{GudxLTj{Vf^qXp5k zkn;>%kF=5=>EB3yl&dH)MlmhqdN-2UgYxf%2bc6+z5aS=E9m2exx%fPYpZcc7qxg#N>aZ9c zJciKDslYxNG{`yTJVe^W`m=bXT?3THM4UiaUD_Xgd)*6MuwTMTm@xx^kAATanh$u9V-%i=r|e`KKYbCZWZ-t5zOs1!o8l*TngJ2Zpld(`@=rC5|7vRf zOYxI`!&onhpB!jrPtnVe5TA@~IDYaHTq5+Ji=R9hV?BkAw+WXbIeg852Hp5H@sssUpapA`>2CqSr9+zngCTccG(|Y>E>43 zT$(mLzorVX6SO_3Vvaw>^<)YL*mSiiRlbj2+a{9J|%N`ErLdLD_9}E|X(>H9|9cHqAQ@a?0f>XlAc2 zXf zs4ml$1J_?(H#lGgNR)LBe$7iUU0jJO#(a_^WEfsjlrLpTkGPdwwZ;`D$B2AXaz2_1cXlX2(l|O+(oX_7m{@z zX%#Fpw`?Jj@Te_JO#^Eaoo%U0G_(Q@fL?=xL(bQqM-8*Wb-vgJKS(03P5loaK%z_7 zzR!Qo;&$mO6o2gtw@2tuJ*u@sB1c&~lA&+v*KXhsyaoT9;GZG>Ywi;Kdzph0Ryey7 zGJxRg@m?i>6s>oTQiL%`eQQVCYM1helWuy9P_0a>^U$hu-=RW?J|ZSluDF!op-TW2 zmY!X9~#>^+=a_&jjNgqZ<2j@<3p;aeW1@uym)Iz?tDLSn#cK zi8rGc;QO$MCD$42=-tert<@ZDcI~5@wxZdPb2?wA~ zFZ!ABIs>PdJ8xFG$A#`5j5cKUe8xX};!Pn^=&bO=14q`uC=5IaGz3Su29A_Ba%5){ znE{F%=;dP!Pr2DV_lb^I)P-2hT`lj6jSzkE+HZy6d$xoJne%}C6|eTt zn+-H2g?zv+0s?dX) z){er@%qa;k?cjQ5)MH1l(642b>V8laP7_g3FNLei4^{grE$*~25$taDt;f~2yp88l zhWn*C?ix3jZ#d?PczyBjr*#5DdG)*UO%6@@?*9JqruSq1kJ0c$GDHjep}}wk#4d_ae?L0?eG~4Y*NTT@#}gbV z@Fek1m^crLl%(>?gpSAxa=4Hfj+LywpZ4NGx-p77U-rVDhEUYQfI!~5pHIX7v zFA9?JCO!YPF}e(ba`9ZYw-DO9>_Z~S)l%Jt0}%{^Fy)S48eWt%TzcBaJNq*I`I$j} z^T#--z2Q~q?1ZL!>cI1vXjg@QOa%BEf8o=0N#l_8SIVD&N|*`s?G}R$4zcc*-w~aV zvH1+JaZbTn0~F|XE%R{fpGcPi%kZWAV2cI^+ofnQuN}lq31+Z83kV&N_lm^wJ&5Id zDwf~hbYU2S6VUn70J2ytHDLZdwWti_@07*8$UVA+^C+A#DYK$B+0bLunW_BUP}4$? z4(ZNv%5Sem*)K;&mTO0b+ffN?r=8;x|8RN4Qn8YkD`uwXP87u~fH`mU6_9_{c|CKb zt>>hlI?fZWZ=e_Pq)P*$3S$t{hbO9)&PT_A_1^~TCCgnWaOL+T4m zH4`rIbPoAUkP2)h2CmZ{K0>B)*ao4;vBdyxmsv{B`V0l1DF|@U0zrUw$UX8Vp*WHH<&s;ZM<4oPw%VvpYXQ`vBMLZg0*XGjl>|GjlP} zCOuv5jshSY*wa0<>}>j>#Wa;j^zUakTl|#yZE;C3Yo;FD+AX-NEg*RDNMVOQP7` z$gJo%vVT>Dx4PPtg$Q&4{7rV@g{kO2y83$zAwwR1{$@+B{+3~q0L9HNH-6xXoMhi&ldzEk zv4ddXe{yK%MfR19=C3O-FJqZf%|;&}zOrL2pDl!gVo_}Kh3VmY^gaQU63=F0@Gsj@ zH8He}A95R+2zk9M!X~1_q%2*gl006Mi9`xnP?m=clPDshVXiD8N{6)z7RjNH?m)!2 zEV_%|lKa0b35fZ^?zz#kn}ZQtI(JKCApTsGv4e6MSX<2J7J(s4q#x+lGJNKp4bC84 zB2hJ^V@jKDp$UFpwE^C~Oyh>8NNL_){cq&$6?$7T$x3c6{{x9;d3)D4_)B@)T%NaE zlecdoFabDQ-u~@jR80(hgC8CfXZ;g}5=Fc{Rg*V?w}M4-=$qRUG4i&OC?@y+R1y&L z@m-g*9A@Dj)W-z^4QfCgWu268+i+_O+62SOjB4 zYj?8F_lY#-8s^~BGPdAITTsA;;{zEcb15B z*lQXJe#AzZ56YkUD|Ul>PcnJr3_`8bSSA_zd^^$#B8%u%fG4i4r}I;nT-@moFOt)P z$fzgscz4m(VHp|ui-lOSJ1xHcQ)@_FC1JvBw-E&yt4Zz+0Gin^xx_##Yu9oKROK$r zE1l)!BKJ#>4r>-BF8mm$s>R=h!4HM#KFmCCB{@*!(ikgDO^)M7GPH|gKu!I#VM2}; z;Fm&0GPRX`rtzP3E>q0GW~6!HyQyPw)(4fR+saK((!|CArqj)^s7C?QKMlBnUn;QF zQ7Q9!ImlV}9Wb(v^%IK)UERh~p(&6Sv%K;P%u}qsxl}i~;vLKx)U7Ec1(!dmn;ZU8jwfY3hI&0VXE zw?&Xs7+)Aq9;YguNrVTbXct($`F~>K$X{z#q^aYTp5HZ~(dj;oRO%}+elPwZs+uQe zFu`Pit#;M z*kY0?L-P|_%bwQKoeA`@Ex`VBv3Z{$DCbK;=6ujQ7{sw!K)B$2Vm86yQW>@V^>O>(~W0$%QyNr9WP}HEF zgh01V1^HfLhv`DGJHkFu^AWQ_nlD+bkhC{*se^tPL7O)uC}>6QNIjY16SOL)=WuAc zo&&+XDI$bWevd%XQUp6i+3_yo2_P-hU#g{&q5Czd-T5}jK_@!4nv5M|U&e)N|MO$w ze>{dk#<;@Mypa1OwNkwEn7BxG^WqEev)KfHh5lh%N`@}fuVrS!Bemny!-1Lb_uPoK zs~M=-hxps?4l2-*oX)&qH^_POF!1TeBIeAJ{gKv(0R%^Mh^hE5!m$cYngIU@a^$@d zu0RlEDhKqu_j$uH?9_7UjSDj2WLBNYJ2HpcAL?Yrm=q~#I2EnS={7UJ5v&`bn{ft& zAI4myDnWeD@>`v3QWTd~WUACZ)s(A@KmDf<vMNph3G*hbqxfXRR`&^--bDN37;i=Oeg*J~9Ojb@UE9OP$_A>hv1w zoJ1}5Dq*|S;U(6l%NwH(if))XAF1C-ar0dIP}ad|m9w@dGL_D~6QHRNM<0CJE9 zBh6zV0Bp}*3=S+zbpV*a5nWMj?z5wuRUgYx0*BoTO>K}-;Vv1PV%(8hNO2VaIGFJE zEO6k8a4-EzMW1vqazWb@*mXpdo@D3^ya5f0b%F(d)vuInnNcuWa^wxK zBFH+!&{gJaN17!=_u0aU$q)i=A1{^tqxBBYgL+ioxZkNDSl{m>GZFOmO|a9oO)e(( z(624PC`B#?7ZVqd`*Vn}&RwY;h_rWzFvlw7IP%l@%;A&5nA%a&)drw{%~%64E1D-k(MVBjetY9HvI);<);Js#TkGY^^PQk(6h~TFh-o(}XfIX}6ciW7)LgSnU zi+{)savm8Ey!5>xU58q7Rp)oZG%2>z2%*_J5WBe7A}{Met#xK1{I^i+z{$XB4_VHX zrpc_G%TcTFF2Hrs>FdmFpTJJ2Nqo!$D zO>^WWfcN4(!m&8y1B`!37~jArF;d@@9wucJ1f^NkauoP*1x()Pl6mI?FfELQh)?sWig^3r9Y&4qb`Z;jxxg7`$5Sg%yN^T#dvwaom{-G>G+ ze;gnjqDGk9h6Y>hhNq_p8fHj3Vw5qTqamGL85(cHP5fKu(OV$~tS zkOB#XTF^hP5mo0r0o16pgA;kYw>h#JVEgoS2~2_SZYeZ^5z8?OYd!%Xp18XCnEv)B z&|@%EpL%lr1!X(NGZkhQZ^ofd+-b!9rD9Z zoHgs$8~Zi0uc2;7gkvC*GO8gcHL%qJD(a(sA;O=cCg|3JTD_Jh-e74siqPO46`rK< zgNzdcv(@kfcneP+J{Hh^snEz_1aWef(mVOq>g2PwIpX7Y151_06f-n5OBZg$S&*4k z=^P16dw7h@mNRpKZ)(;0Q<*+69BQ% zUBQ_@DYO)8TuU*|9l`(WcLfn?H?3F_(*H9i>ZOhNkbW&Q##bAE#8@MRhVrGx z_;-X|J|@zFEIGz!Of*bvXO(hH>_gK&CeB+q7L$T>E#Q6Wg%*U%x=%h9JMxjt;p50S zF0aAUR&-hq)lPB{KHFcqUdI125RE!tqwj~VdUyf8N3n;&ykr=^KxI6}9cN@5BjX{) zmKaw1av$VZqMW;!I}y1+zRS!@JuHmm@8eSqPDB+aVuHIhv)NajJvn+Z91hhWx38Qy z5TuUk%#0ZnUps~qmaNGa3ms71YR(~;Hww}Um1nUD$yI-*^zd!=58pvfY%er@xHK(0 z44M3nn53)=1eGQj9Aas_BOR5S73CZtes*Cqvf9~!L>4>aZKUOV zkx3o6iw=}}+Yok7{3K!PN#I0MThR{7Do0$KXA@f7c=nB98vr`XK%wWhBO&N(JZ-MoUIfnI3$fQjH!QFKvsRBSI{T3ke)r8*N+* zcfcQ|i!qbAsTSh$J;0!!Zcgf`g$S|YibcUdz%&)=_M2`h1fFxUu_#6FK8TIpRQ@Q1 zqjklqrFYDV8c)cU$tt*XvDhpcK@yyR9)_1JmNBcKRO$0!O0@=CCn*sucbYmOHwc%| zVF;UOyDGJHTPF)S-(t7Lg5LnA4iZkiRlunkfCNE%%dqJsuGWC#i9rN(*e|?|COC3W z(p(?5M^TpCBH55s$Oc!lwo|?Zt+MB4Y%%74>hHOT2J4m)4S!aFXsn7( zUu7)jRjLh-B?+f)uxs@NU8{6H+#MoA>DDaxkFJ2C&Nst7$5ln^G@?s)*<5U>d}O$H z`UV@3<+#sw8RRshk$i+6KeLk3t!CZ*cnmo4pLa6xY53rKaHpAFR0u(pgi|Kyq;dw) zGSY0LLz|rbf!$A8Pxb8e@nb2 ze*MA{W#r4BRUls))NPuj=vb1Ik-*o!k9(8ckubvv&08cq+%(Ai(~qrnF2Tf;%YqXl>_ zkCfFWkXv+aJsev@$+qF2S?8L;pyb~v6>s5>41}}UE9`rHuJAh|Z?eKnMf+0uC^5NR z5A2#FZx*P`1Aq`z=9{WA7es9v=L+SluS9@Cdo9A#ew4;)G=c*BI?VkVX*~}~9gz=- zjnzCxFh4{a9~!m1DU%{7T_Bh*eG_!xmNS4$ZjoS0YRO4ap^0GL$t}h_E@JSI6wSvX zWhB#|RUnxkMyG#hBvX6WAd-1L=uk>BcfnqVk<1H7y^TxetVN`Q&>NCTT0=5PeM@51 zMSon2HBcg{8K#)BKqYf)V*p<$t=h@ST8lwlRaHd#Nh3k}ttr%r#ggmJ-Bim)+Amw< zO1q$mD;i*uig?i0E|&BLJjtlp@vQUnL7>bwsr-BhX)1rE{GBa-XYw!Gxrn$YLyvxD zrGBNyLIz2S-L(>Owo>PyZ2(?|IgDt&GiTwL7!~%z8w!Z&FwG$!M*x;;}f9Z znoos-Tu6SYU&|~cG2+Cfk%&A|*V2@mYvm;nFA{WPR64uinz|kw$;2K_JL=EHe>&Ia zAf9P*Z##Gx?%Ki`rZKyLB~>zXyb^#;#FO-EJ4QwtU8R33!yy-Gqt&kqiU{t3&>X;*^Y=4Zvhdo~Ov z%t?Tm=astD7U{`fsfA{?@EcaKYYvFDyPc1{9r(3MHN95@Z-Yyp$eQ^4w)r%_g)n*k z5R^{Bm$IgQa3^(;lN%0<5Znoj%x3DN5Ie%jp^I#-+kB>B%#tIWIDr6?3;TY+ipfyS zB$aP96}0}&W2?UqP0vtPy*OiMg@2BPuX_~^J3N9}5>DNGkzuWAdXQHdPCW6;q)#SR zHX=^NWPBu77-Eq#9+K7H1}@tPE|&C&v$r7@L7oIMlqaXKF&|INZ{^8Tz*~6o!>@$W z=|riCeRie6-}0W=0z`|a5++x3dgLU18h-bjAkFQjq}&l_N60G67sN&d7ni}PmrCaq zcxYr_9j+V%Ma3Zs&;iaD+>L>w+f{I5H_>N$@M-_g`%H02!rdGApKk@Y$G>O#x8))F zaKR@-cds%-3RhR@*D}ukPc&{e2li6vxIX7+=0J)3hrNf|(jIm|DFij!U2m1r;1;(Blf!~Dm z_ROLjX2vOASXNOE*{N1ELX_L&Q}EDhKnGq48H-0+K@v<7k=7-9vLGge*$_fsD<~k2 zyVuu>Otzx&Hq^_Zp5q*+`&v09n)?zrB_aX4D`=@;@5>z8Hb=NMayV`Sop;g>wS(qy zoai!r%dk(u-I~Lo2jtcT6zM~^b1Z^Smvet5h1}J79Vb7%p!6QpM5SJiJD-npEz5(l zuk%wTyLhE)#8v}ZDU01v^Sebp-F4LQC>sGq?EBinLN6f)&#)mkKJjiNNly zclhA2FHuO&=?NoISopQOBt!1xP>J%39WiG8y*MZ*WaQ~SS-}bhT-74I#PSu3UP>!? zpLPDYZXFzANTAR~O~0Gh3rePRE z=LlBjXr+pf@&CkMR$v+gH*mgCG770}2K&d7DRwO`xB9*)8{h#WD#)R=Kozu%qS{Q> zRt_ye7wa_46ze#41FS$9p7AlBMB&EmUoa z$*#tBcXNlV;>wk~xLk(+E4#VNz_i9@wy*&5Gs4m!8JY_Tgb0_h@eee+W!$qxz2j|R z({YOY5}2Np;I93Iw}v93H@fy`5`${P3-6-jn!R-^pvBJ@W81#U-Af(h^bLh{o+h>} zOgH#6-~eR<{AlNn>TvxH++@!k)y0qT>fWgLO|6IGLcDhCz`)}{gb_&eqDYHzN(XT| zi#R>6;~ZJIvWutxg#rhnyB(1=`2U$}%NE1K;P>}&2;p6B^+vFdZ6*4M>e9zgF#Pg( zTLt6E?M|EaQTMSw!yNdT&VfAD*Vj)r^FGtb@w4X08sx7rUZ-CHb3a!CPLD`y5aTn3 zpyf`{8ORD^%ze~CuAsVLTuY2oTF)||wG>xuV&T5tn&|X3#-XH$HyrFoI=J#~2_pTQ zZPEC)+BmWzeLY(U&&HwWJ)7lE#H=E}89xicEKtHtqsYpAxEnmS(N4cPqmnSFF<8M5 z?eX!WFxCM<0!Wd(WG|M1RtRBU$;a?R+B}r0Y+%k|EM04VUdAVBOC1xbqkkOX4uQtr z+YO2P2|#1+Y_YPU+VQcYR1w|-dG6gFOM%Yl0}Ef zabt7y8%;CmtuF+XNA2p!1@A(;WQMj<{EklfSlrH!WGM9>an~I?#Cj>tOAtEgLJ1hHhSY5X)QcCFLI(Sm!F{lKhIBS06hfM@T}A;aIY;5RVLdh${~ z45^z?;CuNp5;1m7MYLcIqajlPMX<%zkcqGBhU63?%}6pud}rd33=QzZMy9B3X?z3= z@QUGZ^Pj7Tvmd3~|Lze_m#0_;O96U}J0h>KYP%kM5MGRrw9C3vegrk^mE6UK{JBu-t_)&&X>QVSdl!_|U8=13_ARX{1N0XjP^%@|3zyQe zi566p3$_C7lDY>7g-tPRgBrrL6Y1>Pn(C!hdX|W^G6`+gqLk?@mjas0o1h;O0ZPPl zWCu*iM=v4dK=8C8Z-`v)A#VmW3_msm#<3Bjz;?X{U$x>QWy1ZqL&K2L*Xv+#neH(8 z2HzIWLTn_Z^0$P*Cu`A097v-9o+5CH`a!(@dm$BA0NtN@t(pw@$jR~=FT z33RB+XZWl~7+-!1VpDoMDUp;q61^3qvTWI%GMfMc8zTX9Kf%=`hDesW$vPNScJpMI zAcRR92geh|4Y18boXBiX52M`sn6m;LQe5P zRz;4>+iHNB)WAdB0nA1Mv;1vn(DLa<#qZv?y#8fnpTpqCZB2upQxd;xFe8F8vNaOd zu$uto+I^5+{7AXLzNw{LOl}UALAh8ltM=;W$Ef8j*)7QKh-9r;-7G&l?9ZFz=d&oW z4F8|`3Yzc*ZVW!fwo58JwG^L$4tYj(h*8FTjPw~*#?LfImLY$c=6?_JLavsc)Ewjl z1^IwMXkYKK(G4TaEfQ3}OrbApjywzK&l-)t49qbCK@!*@Q>?dgMJ>IsIa<1eBhzfa z!7$}rQAP%(0&G*p^hlCMeJC6;Pk+jdyaNP)nHyv)&>EfO_J2?_IFL;NY^@cg`Ibr*8yhP zr2;3B4rNA1b0iClvz{pAbY9MCzRAGFk|ML}Zh=z7BD-fm&r~}IjURXN-`qiHJKJ5P z`s!sod#bO}8HmpM16{iFyC8TN2soox&_;Jp_iylGY@f?>vHvBusb&o&37qx`+-{X!S6a?H#68DEV z2wZk zyUz&|Ei9F+tAAM(l*B5pAq7_zzE6ZBDRc=QL_=wA=3Ub*UttE9+RQur?{I%Md_YwO zoKS?Jcc?YV)FOu@s1jKWazx8fQlCoG7gKqHiWMDMk>iw&nOkgHz{asM(p{Y#@0zXW0`p9rtFe){If z+r&%*5z(x)55YS8L*c4Nq=vIuPD2un1K(VJbXdEtti}4d^OeGS`>@^=zzsh<`E7Ph zh3cJ^1qB1q3y;0yTNE#Y6czZ|DO?mXSHO08%@wfGuWm@d-pN70hBzW%eJ;g4|9X0s zP!`85AX$2~Mete+xa;E8zWZUi`eTBSR~op!Gxx)Em$+XdygdrWm2ZHNb&fbwu*jhs zvS-=AJoq5+A>uMy%K{VbmNazLB^yyz;(@PI-?-=JZxAwHqBS7(bl(PQOf9TIt$qTC zLD4~Z@3km&4%d8%$5XW6S9_FY*8A3NT00EXa4QchS0ue7DayyhHXMmZ17J===*9-M)nHgYbYI_LC zDn^(mL$$UrHSO~n*lc=0Ed-_nzFE`2r< zCDMu^$+_wPg-lCkfb9)-pfJh`ESamJrJd`@9!y(eMWC3B=Va))R}3Ajk_o=XxTDm{ zI!na#q% z6()C+Y)7bq>;m%L)|Sz|A$I`FwEZ89pvoTf6}3pMKQ!CMp~o69dSt3Uka8K1U=VZT*QXnBPp$0caz{aSfZxaX%Kt{;S}eZ& zW7#2c7GukSuk5G8F5`rYi56cw%8c^Em@y$v0WicvNN{brgF+eSv51Y#sZj%^#W zMgO^9ldQq4!?z#v5eSDl+NCHYL&w?*Uj>^1#KupTaBRMz!TpNhC=V=GYHBJOF1_)h zrO)7<5kd|YjNpfi5K$zDLaRePu)n`kaDTN`P{=jYw|N)D^kYGla=`z-N|}W?9iU?E zB5R6(!(|MUT5$Aj?ID%Pa{bzk{>xB>2Fa1@dg%v_WKa_9aO@hwA%OUQQz;P|d&#O0pNa)U@@guQa*xjS3ONO!gMSXdrdaBuWr z?rR6CkzuLgUclP`)c&71n7eURJ#VF4zZHK{ytc{E;kc9=RHM?a)~{uh_GD-gr^Qp+ zMKk!(CufVF$kdk+V2k}OMNx5sWu868%C7k(UJwr*JdR{uRgEZA`#k;C>IE}?jiYP zdyw&eTBfWc|8K>$f9 zGc7**{R)@Vtw=ptY-HA%&D%y+f3si7`iP#k|s}PpsQhgV828` zDUAa8XG{Lzcc)7Izw;xI|6Q#wxS9>TK>lu$uPr7N8*6TW1Y#W|L&qTI1cRs@!Xmvj zkz3kxdhK*dyK_+|`O^^(|kM|i&YtCWqnmF_oN#qsBtN1Os z|HqP$buRh{^V7frlV*s;gMlf=z=9c9vfnhY(4(v~_EnPWD_CUf{~{GU0d%(N6OjzA zW@<|<7jD!}a=CD}el4?HI2c<2&cpcQPMgc3sC24U;>7aEvxZ(^7)c3ZAp?RUHkZBn z68C606Buu6T)TU7j3h(zyjn8XJVrLi(`Zi-i1Vq87KxH`m;iCenNRyh{6`*$^gar8 zj7f5K1;%JG2ZDO^3CH>yWj~D&abKASfkRiM;BzxX!21sWX4txB|Mpo*Fl{XzE-k`OwvK&hz|Il2)^0=XViBbr(aq(~ za#8BpBy2d}6j$jSyAo_YAF|R`e6Fp{o!M~yjpdF2hHdf4%0y%(PK;^Dnmm1?h+HU$ z&{XoqO2bwVVHU1TnaiLqgM@mg*EZyudv>W1>R)rsRf|LVza?phV_EUlB+ai?Q!rKG ztPj2Tv~-+W@n!v5#@rr8xijxhP>J+WDzcH~`jO)Y$NVzJqu|_>g%wm8v%nB4nK`@D zJNpJJJc|n1$ZFPaYJ6ionfOLLvOE!49*?Z<*(JrFS?49JP*w17{Sibp!}amj5IPh} z;Cd@rXi-OKh=p&h_1|U6Z$^DZrquiV>({Do?7Yd0t)b4_qLYyIn&QI97>Q*2?q zE-YO)8X-B8`<%Mm&-amWp>e)UbcFWGZQS4 zroQkcTY(^%U6GIG`}OR~WmaZ47S}Q^*gFX}fhBUhKZ}e;5w7@<)GF3h{ErZ-&p(Hv zfy^9-!p!xr&oNi(X-{3$0f-1Z+Ufc~+`R{QT*dV^E|1GX;jnzfDOI&P?D`=umXsRvF2ZcWnBcR2kERd9#mxQk=>Si% zDtEOn6D!4&xqvT)J@4f<6_gZ9H~_U7U14`(T$X{}aC+6U&)U!kcx^m;rYu1ye26sf zV~lTa`CEq#m0++cEn3F--oTjw%<`#beE)`%9k2-9XwvN{bH0u%!RhxEvw^?7QJI>z z^tNVqiZAelehb{u6}Ubt6z3?z%v_1IeJ%(qxG!cc(q(u5av`)cyN^?5eBQv+VLoW> z16}9_WW|UA`cG69T8RkX;le6}@E0d8t!><+k|k|uC<&VPFJC8dLllhzv!7!s;hRAr z|E(rC_I7UrTrvbw;N*z6hrF8ec1O-gka8Z23la~6nY9uNW{@<8Nm3?mSedasznyR2 zj8Xi)z~QLmJha~Ms}qXaemt|lo@qQ38|K_O1cO@q z#9+7)BjCj4$IwVF#-5(+rxXkq6ngKCXy7uHb3#EmgQ;V&a<=p3@NR|D`B{^Wu5XUc z(?$Nn4#rnjp04aUwyZ;)aYL_|0n6f9*3f{xhInYeve=vD1OfJ6$=+aO`SsM^EktEJ z7cY0k=SVKUBv82ZEl7FqC+ay^rI}9B9IC-zAh}@k?w21O!=p&Z1>lT#qdNgT}Fc7XQ9? zlto%@V=>OxL+E&ylP}$k6yeBJt4aIhLzSQ;B4fiQA=f1Q>iVgBVRRYZ`lJ_AgVIjRU^fX z|3mt(v{Kr^%FbbdCF!sKuq6El?!vTJG;pz5dPff5qz6j{6)vA&D~8voAW2Wzwmyb;>ZJ zQxpG_Tnz)NATcYXE=YWEUw?J9YiUB0F+vAHGJSl3vK{z25`idLW3!9O*YFc6;)|c& zZ}AVkUs6s@KaR?YqeRpl$9s_)tB4mK?_vev)-Zf`J09e9W5Ctb*ws+;Otz9|%1tf0 zIWnb%tH<+PGuDK5JOdP6Hud}|%pW@u>z-hghq_5Cy0+9XT$kgfs9%Da*c27V`GFE$ z>A}|c{g7iO?v$)qnjbE7Jc!|fR!dwbOU<`S()w0RUQmHqKx{zt#|3^sbPeJ*Ai{no z__^4vTz`k^yf61Ef8)D5EP6%w{ZaSb<-auieyjUF#PDRo@0YmmT#W$!diT9|Hj%r9 z!HdF=)A-Tl{C)WGzz7*PgdcZ{LoMi5X}+=!^%nH;#~hhAvk+;fUuMW~@KY8#(I^|!JY$$m&X1YJ-i2lVA|o}&?9 zGvBc$%zhLQ(ljkPEM+}`A6X6uWFJ~6 z>g4~5ttVi1d~_Vr0lL{u?QpjvZBX2qeK3ddRrNd3(KTfd zIedexse5ownya_VkvBF8!3Dw`L?gX3`+*%);Lm(@9ecm3%P!w$PUu>YI?7|*Qy3%X zMQqZJQ2^rPiHp9Fy-rm+8 zXzdN4`Sup(K5#~?y`f1!D4bG+?d^4XQ`EG;8=F^w=THr=r9=uQ7!-4fq-HSqcV!D2Cw7Ec7ZSy9OPnoo|!V zMKD8)Y%3JaPzM!nlU~#&47@i()XR>%4qk{ggS;84!i1?Qza|&zC7zXpy5I?eX`FY2 z9fxhi!v3Y(*)Z*HQ-8Sm_|Eij*fh3KyTBHtto0Te>8qrNbF$Ls%nidPwCFfrA45G# z_>d5}9~xJ{gQgPB+LO__g}#od?C9jIHS8M`yG1v(DEF}pvNEr^S?q9^Nv?10e8vdO zX3Q8ZxV|Te1&;wUpj}aN@W3#)31r5dJP!4gsXm2)?Z4gWNMQd1v4;@jE3r@p8Erh3 z;)$N4&z)Y9ip@(?F|e45kFZAqRD3#CRD8z3sJP<@P_d?`LN*OFoNz4rlPC?)n6A76 z<=()o+sQ8?mMuyXG^m)Mqm7`MM$j3apdXN+dSW-RkRZL&p55X2{z=-uBuOJkQknDO zk(#pl8(d82pXedVjC&Vbw@`oYF)+!^I2?q&nu-yMoe~4t;2zmlH5ff{o~io1P^A9A zAaJMA)pwsopFqeh-I4I{#xhJ+hN@YH;H)A9e0l%~Y%SFexW}q4wP`lMOEYh;T94|fHj@{P0V~5e%b+hA_pnM-YjdMXhOzGUD;{-=H&(@b zu29V9eewg1+fqbUZ^gj)Jnc~M`N|p*KJz5fnz*4^YYwYBcQIFIx+@fLrw)BPv3K~d zyjH_~9SRg`TS4NCHcztgqD`8e>G?i;yN|Z|D38=Qd`&*u;-hsw+Tf!sTYj|y0X=4a#9AHy20{s99M)}B)!tXo$vB`i|rgmsJg+`OeprVl5K zaed%mMDf0AR2<-FYw#aURrvJVe{H~#Z)9{d^VlS>+DvyehP z-Qq;)DaBLU*i6xYhUrL*)XwLKgPd;bE`|{@oLU*ZUobz#I z1lus4%iee-$~86)HraQ0RAu+XrKgD^j6>a{VJl^moe)aBX@I3-@N9^6S|vza;}OHH zzI1>~{U)Y0lTB+Bn10OI3byj;YBsRMGYo8vcJX@XAETZ0%t{REa@S*U)RfykX}hl3 zun#b{ayLY4hY6Fd^{9d~rt^(5S*VU~@@Le`Fa(J=uJ`bnX6a%k-Qp|e$iEjb)2QE=`@zlBJ~I-GpN$#M0Z8 zULL+d>4q_5rN#PdCkD4g`mXCDa<+SyAPo%eEzX%)@xBt?Z@EN*?aYq36^db!{Udxy zki1oZ-8*8+vla9!w+i$U87BhHHPp#II^IWXeUxhBI;p>0>*=)8>=a#kl+QleM~D09 z2p=8lqr-f(!bdB8lw~hpC-rYlh{kp9K;5Zc!oYOrngbz*#46HK&2;>rgOs^WYOqb@ zqdN7-$`Tz(a-Gy~qvFWlNXHDWlQM)gt#FjKR-82)O}Dkg>Hh6sg%$Y~vCA{onZ4*} zsW(LeRi!x4>9n}|B6`;=?GY=#Cz8SSO0N7SAf^kT!KsMrjQCu5LoygYFwd9kiSBOo z)ttZDT)4)#tIhSpI*1uzs9Ya3*7FedK|GKi zyB;&0C1J6x*_jT^?9T?VmIR-bVRaqQ1|BOqAiUgVsjdTZ3Q`9N)KMGM?_58UZ6#5Cpp1cQTJe`3Y;JEujYVhOI_QH^FyrEzx06r2{w!MlFv#H%7w zZ5EMEsf?btUt#q`DlM7bTxQQuW}1bvF+JQ0pz(19NMAAwlz<~SOmO{<;<0|`kK0iW zsoNNs?Kt90jfx5$;9w zJ3@dHH%y47k5YPh_@PQSj5v`a#L>x;6NBq_##|YZb6Taje#dwN-=+H|<)h*Cjx`+-V_B+$fv0E1;%VYMT>pgW@ZH-`F{rd`N?2Y__>hK zl;=F%;-hsw+Tf#HTHiQMnKym>vTW(X>GOii~;vwoOq?slemST{`bZ8pB7#L)qC zhlbGZ)mVala2b(3jl)~HDeRdrA}m4%4=LQ9#`h@HB%;$jBeTM&Ho2gRI$Ku<_)GCh(8@x-hpifAqZ$VYi$!vu=8}ridye?su zM!KAhF|n4lF-k_=9exc9lKuiRId^TMaKB5AjFah)xkJunVeJD$t_RA1*#m} z3Fu_OS&!ne-X|C#eYl8$8JV8xC>*mMf??LfAWq75QcBCZEUDF zVSrhU(xfWS;|ev-dX&vdh^61H^s0ddKd1=)5~UkP%zA{leDCvMb41u_@e|H^L_k^1 zP*&9IkFF~0eWH@J8rx|ftiP<&)Vo$w?;1@d9y@#c&t%z%v?H$hJx8!ISYG|#b$BR& z?Wkxx;c85z*izRm@D{~ck=wX&;;-@WmRer)5hVU8Ek^HveO(q^f(b+i6%p&H*m_v& z-gjREn#zanb6Hx@D9w$QAF`Ae;A8PRCWo(za zR^PQ@9QMMVeWpoJ9c#iF?A#;lBGV?aNmQzt!8vy1T0QGcHSSGl3 z1x57qBrB+cb{m;5ahbydPeb_G4m>RYFeO*XPYB&^-v*CQ=6${_uCgWuGRFz{=Vv*Il32qV772Ps7;( zk8)tcHR35A<+Jr$W)%7=(8jhz;2S3`v5PZ9LeTy?v$tO!wPwqIR^&t04YnlYmnHKC zGJaO%&zIzNb{mV9(b?hLd=8yG)pYjTiv`z>Hp2mR{;bF>+oSFq2zAyPi0z#{(ieDW zzXjeIKP$3bm{~iJwCw=}mp&`9p{q@h`~SmPk(nSo?2Y2WZZV;8>&}o+ekBdWJ$)wZ z-?GUCiC3FKVQc1szO|++W392z`jiXy({u{FePi-Lx*`7B0R zp4_3RF66-0DDG#mBMoTWI{2^^dp-L+8e#aEouNa(=Pue=XymzziG*srN=|xrZ%2^0 zN*vju6Yi&-C;s4OtEmGtJKH8QZfh`T71v|=Bil=3#{D?qrGD7`&D{9SG8xv*Vi+XG zgb3Z$8R1pE-_h&Mx&xif8?ra-_3YXW9Q+32v#bCWX!B?IO)3`Qe?_))brIIid}mv} zx?eMNSAT>TiMU5FbSdt1F6d-*gRC6^PNs1WnRxh(fl6+9`Z=DMcex?&MqoRy7L7+N z`xsVJAee68>#hw_+l=DKmv)H1#!3`A7=@NSei-{Rm-N_$6#GP2GUW7Ean#LrSthhA z7Np(@n34Byym=8~LG{}VP%rPpeV&^Si@>h3?i|6H)NG`K{ip6H$gwMDFy7%~^x{rK zo_u~A^{j+*Ub(|*WZ;dG!i5>^2JbDC>1$XH$~!k!UdeHdTTzS-Z7#hH1$-q?_x6?* zVD((*aL;k!nbF@mZp9`j|ICIspQmmwT8%S)nQH}zjH!+>D2~`z1R?_ z`$3io?bZ>c-oOfi8G>QN7F7Qo&I;=2n>R%bf%Q#cyXBN;ISj!74Db3g1c!S=VEtZL z-m)5k-?sH<2-J5%4d=Of)Ep%I9dBTtKc#Ai`bXMD)DT=`IKI;m91QPAKZc;AvxFfy zV9jDfFh$i=WC(JL3RGZ5BI9CH}3}DENPLEVP1GqG>&&7V7vouhipZE_2?ZP(bJ!N6tGG+Cw$bj_!ky33^3c?* zbpbWEA5$|D8`QIB(z2PU=|CB5`7A4M0q{FN_P|a-nw*8_JH+l`on;(*e03wVaU**B zHRE<~_Gs)eELtG$1xu>{S($Z^WXD7dfTKL~$zZ%tF#E>+MlO@Q5Nwj8)xkz!)Hh3d z);C)Q2ugcdPJIM4&X%{{UBDJ1w7z-c;wgDD{g^!JHFfKX)c3((2oxHI?@2|r15|t_ ze|bqN4%0b|$Sn#7`65f(hN;q%=pfQWeLYjD0g^{2YIh z^BzTk^?j$$I39nJ^X}EJQ)jGS-*;+q-c9ipRcV?vHyMWs|N`LKp$);Ly0xlO(2=J?{bhJTxq zpk*kY!{M^Uf4LAfr$^epn}Q2%ie9%_tl-jWXglqqD*x+iQ4%&VfaY*q?9?##2eL+q zZZ6DWi?DXfDR|kb$p{=dgZ&IgBp2g896vR=b`A|$$q`8dHdB@wk=QP3jz|;>Qa?S9 z896n%nAn2qeTB3$d*Ibz=P#gFgpc@ck<_$CeS#}xPih5%Cc)7yXhzH0`p__sQ^q1JshXQtpwwY2>QInVwV!e{v6DA zW;1E%;&}Z6$cTPGc(0*ACIbXYZE%4jyd6Tg;JIW|lj zp_-6$uLo=w94uN1n6h*6$9oWl7kJ{1v z()b{8hw;+_*8XVGHq-_2kTuvG)KU|LJrZX|kYa)$~oZx|$^=n<~{Q+D>1<;!W-zgFupMrgajFt)HE>%~IJC zo23TRqyHNN%aXsA6*aWM6jPTc^#B8vy0wmBka+l9HY}_QghvaJbRLs9Hp4{<$zVGo z!`Gi1z~LbPGjPbH9Z)MFe^)KHKk@Dam`goe+~WomKY&*@_qO0|uFIvbKjAlu!7eEJ zmgR8JU&*{k;qrl@mkNT(t1 zERBGWH?v>2`a%_K2d5h7e{QW8d5JsDF?m=Wk;lS0WL>nLhGe$}X}z4p<2$Z?d>rn! zrtBfw2mxNB*F#LqyGVn?Oe=kpULh{cFq2l>|Dt;eUT!Pq2Y7U=VkTzSSTA1@ z;kP0+f4}ay_uY$OA@RTC{04e4>bse+8Es5NcVYn~HyxwtyoIxR*6^hjUIysedtM78 z%B4x;dywLLZf>LT{r^-=s_y|kJ*$YGQc5}U3@pBuRM#R|y;pbKdrnqH;>;q`K%1IN z3dEi;eb-vlyRMSp%aFG0v{N{ZlnXiIP1alKAkjKrBoQ(Jzx405V1(w0LmqqeMr2`&Vsv?V8F_~5MDk6`8K`W0-?9y5qvr30OCg=hP!h2VYmnG$Mu;sO z{9sro%k+D$Gd0GZCy2j2kXdNJ4P2jYL+eO<)`zgnZGgk4HL%rnPJa2~gz z<{gZvydaY4RRLv<&?uUD2*olsMpel^$Bu%>#8Pny$arLk#4=0+($HMzaDlX0p>f0< z(WmgY)2Dy@D%60mM=z~UqYJcprELAhD11*A4(!5;d(KY_v6=H!D^b#?y?Evq8^ndi zo_EODuZ;UuLZ5!hgOA*K%6V~AjJapv@RUC778BfZj(wix(%xA>S*9}?nLPg}^Bb0^ z?6G5#7yQWey1Rp##WgvvV1+$o4w{(zZ`!;yNczKx@}w_&51FewF6V5Xk!U^S_?U!C zt7f0Zyx!WZoIP$HZQf=^n3Cf*Pn9=8;>wm67p_~Z$x2-Rg&s1?#kot%*Q522e9b;z zYBC-6kb5kH6qXi9VWsSmxQa6DA(fgJ0vpYb#Qxjm+1JZ~|4nUq^A^za-{~RWt~>5+ zr!978zV(oME`yx11#((ByK_Ko`3xRVv_K{21*P#+?ln3`Q8N@<4U0e+_K@LkXVI#D z8QP%!E!xl`i+0zAkk08OA}l3HY;j76tyuuxqMf-^7Olc8S|x*w-N})$=aEYFkh6Kr zt)xZ!0^=yL_Y-NwTjjOlEvV;P|IOa)xtqZ^CZ`mB2?J-n*%w&WYN&ZDW`>jL9|6i5 zj8SC0S?w|?!IA>d(($@M0d345@<^YYa_nL zd*okGlStW!^%NvlaGArsvm5hsrUvNc`0d!lexn$9WIHys-?+G-CM%QLu^GScf&yI2 z2fV}i6CpoqN9=Ai8y#9=i6rYc4`Bw=D^XsXyDfRCJCYBFIJdQ;?PkC3irxKx-n(#S zR}=|kfw{vMPBVKqQ9+=&Z;-h7L^T$yR3t94XbWg+0v-yd73hMRIy#S6M_EWIbdEPU z=lB63u-oWmGHQ}@9QZiUhmjQ}w#&25K9aKNvg`GNu;P>=lad`$zw++3qm9B%rb*5euGs-3oyVlSbDaj60M5By5dwS<=XwVh^t91US0i_3 z55<9}{+#R1ku)^oUq2qpM>+0rKy}Tcs=!T%R|aiO#eH&aG1aDdB$d z6qQuY*WToO?R%VH;;||#;cIsrLw(H+$v!j}1>wmEmjsXdooE_OSv#lFkH>wG6-JNy z(Z{RYGA{59c;W>*?>z3+Vdg@Q`%ImTp$k6rxYNr#j1?O3GTR{YDjRXzSP4(q2&e56 z4HCaPmgLwL#!j^sR0JBLSOm7fc2Ppz>Ystr`Dq#~z8B}W#nll2zZ)zRj(zzlNURk> z9*)%lH?}pRv`_VwtdQ10k+DWn)1G$)B-114HZ!{RBTlY#;G9|dVX3Z`v|X|El~~kv z?CN&4MdR@)JA_A(co>uG6+voxB3vNmuR76)1!5~@e*hplDvwQTvOe2t80y3$`7~nLq(sbUdsgLQw zcx5wJP%M4A()R;b@i+b^psWLii`Q_!E%=_$F3y6~oTFLcT+Du)oCnqC9>Wiv+4Hei zN~31gpgSOVuM$l?-1bIy42<$FvzE-uFO&?dV^09j@j5*i&MjZi0hCja=sk*5xPlT3 zKg6$0>R|=5>+o!H18Hu2G~1u=Ho z;5u{cT;zF(utR){pzIadyKXGWQ?iJsiV{5S5z<_Sr;WpO@YHx5cv>yOlk0G6r$(!2 z7vpKyVxC@vFRNdkh;I@8S7cwmp(Ibc(OYKnboI1NRGzM$PGc_CLm8e92-CsSf@8rG zAA$_)$@O_Nj*ReBL)&uhy1-dit5=N|8rN=sY=kvtd9|n~AB*1-!Pez%v%JmyfW}Of#uM zBP2y`Bl*(n87*oNmw6(cq#un+#4xxPFKi`ctw%wvFua0#j9NTb%9C@u(A-hp-5a@^A?X>v(~f~ zEWas3pAJ`!SEzPnbmh(#FmW-;q-IK2Y%%ney4kN_UAa8q83ddh1+;a|S8U4sidxjn zv5vMQ$cOZ_Z@|pe+{Yu0{)*nULoGjw*}wu7PA0de4KaRdwNPB?Oa=C_ACVV|LwGdv z;{Eo+d;-gCtZ8)_>MX;jUYsDR8yPU)5$f}pfk*v!t!YgLINBb{+M)jV&?xF9fW(rw zAdev=&kfGv_6{tJ*0gDRN1fZ=(dHH(cH=VSaCz!9dT7IL4s4siE`f@%+s)kDVLKMs zFpG2Erw!fL8E`~p8|Aw z$tTk<5xdp!sXiL%@c8xk3^6a&g4&U4qyvB%gA<$INrXN_gck z3|&ua>RrF7pW6gn^=eCFnW~+cf2uhRjIk;xeL=zdi@=%A;zkc8b(8GKG|UF|SB+L% zv=uBERq+en@UF$rDNks$Q;E16WM!0*J@9dfum@SPz>X$V)1=+WLE^3uI$RtJR9?85 zR5PsOjn1CP%<4M!yufm)WZdjv!alyA?Y4iyY7M9jx7%*qN8)J>x-qBN;b8vq+ilkt zb|wA(ASDe~&TwV|0yG2x^n`ZBf>h5WX5Mgy=;LSTBGJ}rnl2Y= z8=*j%-QFT&0OZ)D$n9hjcjh+;IW*Tn1s5i7P_OGIWFbOrt!b!vg+=`iMZt!>qCM<~ z&iGAz-=WEQD+8*&Z^Vq@_>-LXHF}`>KI~e*6?yCXwn)x>3k@gt7CRRR6O}N@2TQ*xW>)cv9#UQ#RIGH}52*Zt&&J851{^{i#pP_ncG3u%{Ln#^ocpGFR{LGX zmOUED0&{lPWYqN?_~yo!qS1{njb5l-(R@#1TO|Xsd0bZP==l9Rv0MJGcF z7OiQzhU^E4K?hQ~#fsC!l0HK>e~xzCyHoqWl6ej{h6sap&@{CYeLvMR72E4kJ=2Qo zncm&C+2 zgYT{`VP`h+Vw|(sTwED0u4(9T9HW)%U*Vm-xM0EjhLdx!VFwyyiWssv7(ac z$0eE&dxy+Z$ZwE1{eZAS!hK1sHAj5wB_f_Zmv4qMx%AVt7O@721Ao>J_-hSC#BA@R z?Kn6n51!{>x26K1++e`Cmq{EUcxQmI#a3gElqLOfazHUDw~zyH;z)Ti51uFGo_PhN z%>13WmNh7+Ak@F_HSO6o^H$Jm-zT++Q&P0`0qv*)&p9%Kr;TR1&A zii$j360<--;zQ$#-E6~8Jz0P$SZ%oBesr_%K7<1Jj#BZmLkB_deTfU3?zP`^TuY1# zEbQR#NMqs8?20d_&*Uml(|!2B@P38?Z?*_zG@Wg0RENC))IijIkq-M~2WOu0a1T~H zcZ|*?K6>5NF+S)r-s3|34wno4dO@hc7l&eT?}yG}gUw7rvttXDgh7+4<$mK zk5hL>x_~+zA4NfvK9H9BA~8wH?s}hccL7Oveob#wY~i$cEpgoK|KF@7Vj1Lr<1JRZ z|4|;^aMkxaZn45qo#{m@dqqo~FKFtI1p(~Ww`iHA&Xs6YvF5mu6eaOTm-+v&rOpkZ zaBiuy8&M4U@>l*mUWdwR!5VoJ)+rF*k2YnfXMeSR*$rUtBa=J2dTLut^3NSQIUnbR;Lkstn59HNmUV8*X$2RhC}?$8ZU zd~BsHx&k^RJL5zs;+`lE-(_rd+ZV+0iQGXD3$J6(;mQ;3OYE3q4Bi>0LTE_Rg7glh z?yqV`EzL(QwW5~VfcB{nFi4za(K5?#$GJo8GX}%IPaRpn38q7 zlv%Ji#0H!(1cd4$6h2k%t?@;-j@TgQ${gz9oll?B^an|+Fv;%y?jh1D`^`gdJ;`7{ zj&6y-@D2|V=sgIwYuxBp(-&*keXd=PnuB(|8sY6KyFTv?W&g>dffV|P8}mBqvm4d- zZIHZR8V2;SM;V{TygPikX))*E1G>I7IziG?<*b3T}R~5cCuSook@!IOw_r{WE2TQiq11n2)Q}9Srmk z1Dz5F{Rf(3?h*&OS|R9XV@2?G20AhhdILcZcA#dYAqh;PRq8_qI>2@XMjY@oLr=pAv;k1*%6gNp!76@sp8 zpmPkgJ`UOr&|Y}x!sneMRlYF@>g5}l^X1$jBXA{&k@j6BzTFMw_&Cbrh;lu`4MX_vjw<-j^t>#Jdb^koses>qM5N z`62Vnl`-DS-3W3y;m=m`lmd6=MF)NpVV|tG2ojTjPF(1Gg2W_?mgyyS=WrJ7{wxv| zBu0dp!)xlcw5t_=$86{g@|J%p3Zuo@%m6rM+9@nkMn}d6<^+P7&-U{&H^L*uU9C98 zWe&$o-+i^>*Cs~}GnPWK6yY*mt$0-kC*LNPJNlAmu1ZS&i&rb&`YVk_v(v$3be#3> zaER%9Uak1I8sP$am>W(U5xH7%^Pi}v!RPVA;UoNhS1X?T&WLSIgX-c#M3eQprcn*HK0#Z{rP5P{~tGC7Z*{p-O64*Owh^muSUR zG8`>YH9s*xwt){r9Il5$zt|(y>>vlbx&qg?2K#uJ8Os)~Q%6|(=EOH$WdX0Q>~N=u zshjF#4Fz1dL{(9%XD2y0MISfNKFYrwPh48txJM-=u%W3rQ?<@cV%=?@V02)100{Th z6K((7dET!ww@evf@6MwpDC)Py`}8m~33kqdj8V}?2B3Aze{1_(z#mUhRDcP? zLzIIJPZW1`&ipE}zrrO);IsvR5l?j&(;P1Os&GF?440dzsxUdR z^879VT}yzm!-2OG6K(RYH66)d0d14fIGhPV;>QuZ%%*ed!s~pJ3-|ME*W>01y>&2( z#xs0|MnCMJG~hLu*yc<~1l56}-7QdjfO+4pO4Ky@v}vkcPmV zU&RIjrZ&{v&zOAA(YjhKLWPqDn$+~OiUbTuY6Wmjy1&4UoJ`-wM5BLWlm12#yp|AY zJj#=SONI>shL*D<%><3iH=ygjDdNhjhoxom|nb)$O(q zVM?{SvvFp!cSPPPA8V0EAT5TrDSP3&l0vKb4MOKNx9rZsBrj^rnshAosul&!4GeqB+k*yH!X_l7m{du?@{DG07U%f-+O%hR5Z{&tPEhmN?9^h zhdsqgN*vC>JmB|Nk?b^y!)93$2AS672TaNIY$7oJHzyf*=aJIF4!$3R@#rSLwJ_Yw z)vM67q?;LE3H)T!VcEj4@C%9=yFzkqhna%$p6Oa2WfKEL^pf1Gr_-RSQGf(yz#{g9 zzmlh+{==7&7RE3}{0J6{9mgK?=rC$a0>(3{^Y(Lq5Ecc=5D7^EQvIs$7|!~B@;;SM zRLDRyn0>GIBt@HAodGrwD@}&v#rK#V2m#KTGa;6KuceC(n0R3`q>U4X*&bu#F9|8w?fLG?f}-kDwhK%C%n-2av$#yvDYVP4Z9Y#wG=XwBP!KVuV# zrB`N^4Ax)VMU5`1AV<#2@B9Nk-IEm+sJ*Le@6Eb8hZO*-AJ3*-#^pLeQl|PH24*(~ zya^Q=53C?ddgC7wsw2f_pGQH_9CKTb9ILRZWt6r)@*nM6j^r7l#k&~Pp=oN`C*lKuAEx!CXXz(DLm_ym8@x=t{d zY2{#6a4;7V<`IK=%wQfb2lG5`uwpq!5a#pgD(4@IDY;aSf)cb{Z7}JBtO(1OdZS$S zU%Kpg(S%(5#jrnLF8g>#(eu!(+)HrR0cKl+*{&SSDy|4Sm6LebvM(x^{dF92A=5Ki z#C^xAh*zHw(|=P?g6UffCXGWjq-bj^Vw-YBob8Gj#v(p~^9C?$8_ckBFh6!M??82O zQ}z_hJx12O<;Ysykwp(2iuku>f3sZnf8lN^avrdVQNI$*D1+I)9L$}9nQ;v7!b<$e zGVfU~^BgN6{rt9QyxTYB4o5?1${nn~2k7rU`dg#FyX)^5{oPT2x8>j7Az{zlnI4XI zrnZC)+CDneM~C@ng^yPH=u$YR9ELt0or(;g5kj+mr!l=0Uux?YG8mYgw<+tnGo5vK zBF9l?{X$lnDF9Xwq}F?ZW`@0l`|#49{E+V@wqsFvaM}|<7oEt;sDsAV&kfiIJTYI` znGt;eGr`82eQ0iAk%5pnW7P%4M<4O^V^ZF47{t=&D?Mlr^DFKpA85LkUd2=j2W74r4qt7DqU$EfCO0!s!s3lOHc5-*J~qA8oGcknwA zzJ8(j;VpUAGR7#&Z#E8p+de#Iauc>+V>vMQ6FAa=rZucX3{k@<9t-2hF4HWS%$}IZ zxq+k2wC1qOY!oO|o?u|Yj|4Q+B3=-nM4RxugQMBGzhL5w_F<_88pve0_Hpsusv*`z zy-9CxFY<7`v-l;@7M2z}^m*RC;PDuhmbTQIY_5ruD<`{ZL%-whzDBK5Hf4fEl?QJu zhJxu#cl&6Ek1h$LI7>n?yZ6R!y-jV186hWTLet*?gIsjhJ2D8;mZst10yGhbMhI{i zg?M7=%}TEtXz+uI;Abk`FlMZ@#&m^hy}6PBUiwrZyUgJp-&#`xkIvr=Bx4{p4x2Fu z(6G10N1ynJ)ORv%$8npR488Kv0g44F?0^6y1qcaHc;L{o*%TviX_CM_xMmOOg3mOo zoLKs$N;e1`=ND4Vv8aVC+iJ}XaX%dEYZkB9GPfJzDh@|sW9cETl0p23BSAdgu_a~) zX>u$JsboOjZ;+}(2dQ}ZGL;O-*@QGU+}qXBIg)MDV%3dpT|o`1gt8Bi2 zO}<9DBKng-w7-{d_hjpat^wQfGJP9K!me26#?FqrhAi@ll zEXavs45OAZpPJ`&X&NZht7V1w!f3{dUQ?TKoiGuRI#b}>YubP5L&=a>ng#8BKY z$0DLbeU#F2UGJePCffDxO8j_FXbj5_XRJ<)l_nPj7!B~3)oz^uRYkGk!Dl0t#ksB* z&wdLm*7dp)heb=o-M1K=_yWcfRsBNo6YYBWp|I-}n01{MI53ynlid>Do%iZ8IgIAJ zUcnT1y+c4Z2q7qp9>HR&s;*aw@CYi8CcK={<(}1aF0fPs#mGpRWPR6SMVa(m*ZZ6o zK-l&66rFgg6$ZF=y2AT=SHOBxZmLR4TWU>Dtm_?TmD9enuTd*kN^4Q&K@`}Lo8p=l zMl%aUHOwB1h5=wdrg*d;ORV@OpwFaMVqpE)ZC|M|O&7(@Y^^cjxihAC$d*PC&J||R z;kE=7|Fs(gO^P6TQd88IS(>Ez_TQLZHKScb%w&Ld55X$EQ|W=qz>URBh^61Gbc4V} z3j!?P*Cl>PCd0-dY|bzmmq;w#*C`pyCAQ30zUz0QGPZu_Q|>^w+FAa7=gJX4;`$k$ zkNVGSRBr$M&U2A0Aax2bbj)B*eJ{D%49Xx2mI+bX7L&@gmely3YFrb zqkVL^kB;zBnw;^}W9)z0DOy*~kktV#%n5@*$+@mSnB~ff^ar;c3c}z;Wp;4S0QcoT zPK>nv?!$^w2lWT~g8pDQ3(sfx!8U|Yf8a7i`-2HS?}T>!2vRGHYW4?LRs%MuzL5c{ zW6^G?j9WA%LlioC-Eke=;33{Wco3N$(IFxFbrl&-6r9~l~cao%Ak75ID$ zd&$eENGePLJ_SK)4CkWe2j=%6^^sVRRFnl$S;q=-5I@;EFBO*|DwREb-U;pcfl2lb zgrHQ;C-R_rp33OV-hy?U+~%)gc_v4YEo0or(ziDFVFtfW5&X&q zFRn7p;wlE;`x48y5;2n#(dBLTUZYrRq3sqnXS+>aNGxr;l?>tRwF!+?TD9mPtwzg2 zDjASTgVdbBK`!I;;iZjLW`6px-7IU$>N!i5iyOitSy1++z>wPz-@@AmD5men_(UUO zIT&9{kkK*zU(Znc?8fj7s?Da#=d)e30<^hkrH?MfR#o@4&qsTFH0z^XKHBZ09X`6m zN7+}@l7=AByj@f`Twk$-qtN<=3;USLD^JvCwFA{3lC$#HFkow2AgrHTNPUJ!LStvU%{$!7Ihy(!!Pl^qPp zFRADYq_VCR;xatASzanGL$t5h*XNzkt{*|_N%gsu$`m3Gs*h0_nDg%p{u1CXl*)cF zsgyCk_n~%k&tZcqh|U{dxLUka_BS;ALa9{d#})I7rE(CK2w{Yn0(=UB)MpQdQgI*d zQpHLt`U0tFZ$KX6O?ctmxWZ+KO68|U5K|_!>qn5fM}01(vL}%T)%&T8&g=<41b-i3 z?`|lSiG=P?oq&F6r9n~p0@&^51dJCJ9p*JNN_^VUni*jixBLDXg_ zP%Vy=YV>cU*pc|TQL=>EUIw1b)ikUCL0)t8J@OyJrG+*q#(*^nIdh}=3+^~@9$t{_P5r%4{$ulq2>hI08Y(A4#; z9GBtO8?!<*0@jzfRXfnPi#QM5fawXA#A;BDCDKHFh7AQ?atja$#z3Mm-mpYz7 zEIrH9=~xFdcwr0Fbe*677F_YxS+Nz?{4OZ(4Op|A9 zjUnFqw@^lj4;a`0_m)QOuJVf#c9p9``dbPwNIa*WEo|QslN*4T$bhW$$Vk*< zE)y9>BQsEeO+KSb%G(;p!8vC~fKA;lPNKz|Tn(R>F`8F$&{>wIu&6!OUhH%!G`? zm#(AeF+)W#AeqLhNr=j=xXn?A7`3944e?(h2@advGbBo*dxrkGKQ#MGD1>u2rL|kB z@Q$5XVle7)x@-r)Jq9>Z95uK2gWCG&IsI(M=J0!S{p`j8!KV3u%f_(V4J?y0V@yyp&$T4#MIgLOd6CDt#J*< zeaN|ktUz`ZP5^bA_!f+9P69KWF#+uKWWMPiYOLQG1x$BC!}9%Xe%kB5qTQ`|2nikc zlbhJ=t7{pUX?zYlY;zBAi(f`~3hd&PC&g};vD;n9ZZfUifu{Y3nG`#ai2^MgZrqG_ zLm;^&#DUFZE*GMseRLK$AuPU9Oc?jFq21yu8dP>gx&7`@#k!Nl`QbN;^CK7-=PzNG zYOX`OZ)^FGA5+t%!G?;q!3Gu>)rfE3 z8=Nm*5hY*_%)ES5JvP{2Jvh>iu<97d&9MaN-(^2%G2`C8UbT>A zV72g5T-}^&v98$x6{z4VivbFc2nlU$rWj%^E}1^ipp~J%L!RfOFOsg2XuyzQG-hRh z_kq9QeNdXZ=2MhqSflwlus9?~ znv8lk@G`=5*km%9#MCcjP?5Z7Te>?jfk7Hz7_IbCwt}hlmC=Zg(0yHsBTYyrSYH^; zEKoriGed5uu!LnthYE>#*Rl>$O$^HD-2@kw?a^LH_kN4m(pZigQR2#_k?7=NxCJA4kB^&Zdy8dVLp!<5jwy~L5i1L*H=MGMnT`K z3MAMP7lBl~JfxBV`8XkC{oErzh;ZG!)ClflU<8kxKo(-nYWOkIZ#Dq&86>t@FN%b7 ze~_m+z9BjKp?tB9>*a7}RvOoNXXdda17mJ!Z>h>jrYA(QfcwQ&Ff%{Hw4!H{CWKvTYV1OrBrN8^I;Ny)qlKza+tF|=wt%~4pGx&Jpi@;x~ zbSn{!Qw5i3d`^Sj8Rn&yX)bOz!yhQrpyg;ci&tj-El<0-kdU!<(-^7dOo?{$**JyOuta$VeRw)#`P*yV3GT`P&}e7hNmuIfA5%_@;B(EE#kA-68RMYa$d zYYpjx*?HoJ9em2$gi>{QSbHHi%Tb2|jT;#>XPy-=-+Gk{E|#pAFWwC)o`@-3vF+P& zY*8I0;rF2_fXnpa%J(uAc++N^(doDxN3w=BZHDnXo^&?1aE2`;* zewacJsyik7&g>f-LP1ZCc%e@)=>AtM*5#V`56#8veDhr0W=*&%o@QwHg&t4uo1a)L z72WcHJwJqYZ?6QYLvJ9Dl#0Fwsgx*|RP@EKShx&NCi7Bp8KP1-(FkJ7gm(Q1QfseC z2uh`m@(HT%l9)QPPj3MJevT_S!XD}ugvOGYQ;EXK^4V$CD|HUJ>b&AK_-L(T+zH6} z<_}tfBt*SRC$T7;|BWQreTDgHTgh3pleC>UdJ%8?A4?o zguvttB9?ESRHJhw#g0TbUA}Ae2#Mi!b-mCxbI1CD+ehV$)@nHl}8Yrpx+a)8!>@x_l1q3<-VnVulw$#-_`x3CH5erWN{9 z=$jwqDlF1|R~@5zW(pKjL6Evj(++B?`*4z~PMWGO(A0IT9GBt9DoRwod6yxosqopc z{W4`jyM6?zQ*embH1!+GB&Z&uGCH#xtq*RazWGxL4Y_-PP79;%5sgy5c`pD{pi6&D z4#(Yh84Al(-_O7lX#YDynyLdTZ*{URt#e4`zk#U1h$qLe3yr>sz+(2n$7|6tW1Zs z!upye1rC5Zfx3HqEig@%Z~pE}DWk+a3~bC^b$WpVpp%_vCzG=9`DVsv`Sajx%v0RtTG07-W{sH!l#( zdj%=q{4+2tz|~>6mIzmD2sZ#!IiXKC6`hYvh>^Yx5CL>M!K`9VV*>mP!e?)_)-N26 zCQ;Hi|CSn5)QX;Nh^tp+tyqG?=6v(Q9QDorn5)!B!|B_h0^2buC{aboU+5Ws`pr|Sgp@0FthFfhXz6F>`I>2{Kv zX{@gikz8lG6$O!n!VdoTv!&i`qysy%HM5&UwSP*`q+FIOI+kQytg6KfjWfoVzs2WpLhb zL+Bonv7pgjdN4fB8{UELy^`#O-tY=9no1vK_kM7{*n#q0~CfR@}!c*A1}bYASvV#a+Q zs209pV6`xTZh_Q@(!j~oh;%~r!a`6@2>Pf5b)#?Fp!!%9jW+DzD2ms@R3Td5cM@31)ZVO zh6)MKo}9IMmpA;A3n`<-rwq(sRh(4N8J6&dZxJaG?j=eo;te;c0bfynnD>S&3u`Y2n$S@xCDh@Z=S?eclM!)RuK3d*G0OhQ^XS3Kqo zzc|A>NL|9f^z^c4q){XV8bvY+7@W`btGpXc`Q!ti&w{i|byfPnADAILqz!mTSCJdd z(xjPB{E_KZ|AGGncr`-gr^c$5Ps5H2_m=H@(8~pPI|3VS`aRx6Mu}^@c#^7s| zZY9FUu7XRr(SVmVdVPZ83P%c}CUlaB&raIq;tO&ry>nC>n>&co4L7XV8@J+@m`Y!>uT+xC=R9iH4UPz) zxyz7rGcY9GHx|;q9G>WNKBF}-&oi5N56S4?6TL8!1>BDXhTIZ-YXaPIRMS_V7TBZQ zsZjpVM*P~E_cw#QEUZc+EmV0r(;ZL?*RYp_(R>psXgKGcriMchG#q9xYB-jrhH+sV z)2nh9fQU@$9R@fkD_EtUtn?tR_>uH|m2MCjE3L7F;=6jkb)e@p(_E~xJ=Yi3`*P^) z`Jdz)htt{mn7S3~?1x)W2r->~f3HHFeSbid_F|pgw21cQ(AlRxj_d5my}wguSBYeS z&Ncu;ZcTg(Z;>#*8q%M!$H0%pamGqj!y(uG7pmdgj|!{dt61$9SHmwhFRF%L)W)d| z5+_vjUkz8`JzcBIW^!EU$lc8lwuh|T_z zSa?3ewHQ!4?~uz7^$wlu^G;~j51fwzCG6_))Dh_qs&itaGdqaO51(NRb7)+SA-3Z{g2RJ~X`Fd-I!XB}@%cKrxa zUwlmn3N=IILG@)SqceL)B?NNadS0mY2JP6ftu-Zmv_1!n!YB!B+>Ud=N~U@z1~yvj z#n#c>9>5Rw2;uy**cMC)Fu*}n4UpYJLoGgmgs3VWx2dGY6u>A5Qk9yyP-^bOqpA)` zO<_^VKAlZZU1K0;;Sed8hE>g093)Nrp3 zwIgimg){CgP8A1i{!WL^S8-JvpGSeEHerC74AV^mfA~v4n7UnHxI=?ci2LbNguBJ# zZV*6CJAzPU8yU44=zqo@tJNvnQ{v--I@Rh$46IgDzbF6eb!1&y@1$;g-7<|^xZm*cd$IS^ScE{AT|7Mfu2b;?L4pC;BKGs0MF+6&+r#YN|t0h0F(#84O&CG&=`I{hMDz1yNwGCodp^V)ujeT zI>UcqVG+bI1-vN0A<_nlmKMl;7%v@^1=1H-pp95$KEsSp$di^zT!yFx3VhxP?fMa< z_7JVK+b^)1g6h{)Tb>0NMH@I(q8sp`Z$y zMPMead~yN|A|ZVF8iY`4fTqOgo)Qzr3+5_H(_E$F3Z_^687#qQKg%GN-m3IKWxxqQ zz*u^nr3+)mN(<7o>(1y;z*SF9SLcyU{6|4*jss6iIIbT7&UVq{UDrdhyAQ&Zt9YE- zCDpVSybcC5+-=a1-q~z)WR&iF2EUYYh89%Bhdu*|1p+VfS((p3#B^qVnbp)nwdo+O zX3Ih<8MRPtkWwB8X^K=9Qpte)^6mT_z}X_|8EZDRG3zR3Pp`omxKn%P_-Lb|9CgY; zpk~UcU~HdEAA|JVy(l3Zf;O>+g2WZjOsJ7If8eSRWKh-N=4cmnRayFoM6F2nwYgIE zv;k;ys~EXTO?RBdzDDr};H~68e`V~g!;t-TErd5KOQvefS=7A^B^;59?F0##`p`j=_*alOaESU)K^~@F?QG|3pM=Z_&66u)3_)CLu}>Hyf=0cfjzMS$|AK z*qG*HaKf+2pv`)QR;aJ5tu`NJ2YIoT{BWe?`q}f%v#-*|)XbM>Uq7t-pYOW=C)Pui z>^)SJwUDyRr1|jd5B7)Z$fhIh5*CExeZKe>7cJ16xD{wXrg*13=3C6oUref0r8>P) zyjX8eUkgOusO-(klBwG0OzF+W3`}qKT_C-on4kk~4B~ookReb8tZqsEyor@Sg-2YBxhKl600#gp&t()5X42fb32Ie&!a3C#Q%%)7iDXpRn@ta0U&7>tV`UlU^ z9y$A6(leS&9~#L5@vZ+a)aOs|Ewne6no4f%3y3U%UvLejoF1iy&^%gNw`@z`0>SF0 zAhiVkB%b~SuIYwrmT<)wKNb>(tm{w-uJu$xj4sO7bDxnq{sUhFo#9&n+Hi9}GvGvu zibz*jB`9c2!z&!tB&RVDdK=m;zb1oeE2`w_zT{axI@L#~`6%hR%;;JAzNd#+vHHi7 zO>5lP^Vmq9`q^#P;zj3}nl`m~{je4f6gt`R!rEv|TVp^6o=jeo+QPv;rDRRnW_Jf| zf+FLJ)a?%`G#jNq_EZ#@fdR3>z+@ngn}Hp;a>OSSFz!)mM)l+fJ?)2*040SsNhuYO zGEtBVlmV-}$bi9D-#aA+Ut6@BmKZ8lLjkHv&(-Cz>RyVf47p;w7P}W>Tul_tXs3AC z7vSnC&M4tHVCooET{69xBRrg@!#9+{XswU3>VIcnNBOU#T{I-2Dy2$F08k}ScvS~kOj3(J)z<2vYKr3)S#k(dxm|5uaJ1p!lF>5<;~h9gj#H4`^t z9M=oN@>4WXPBV9fOg-yzmx@hvoSzh{Nv=@nnV;+mTwgv8y` zDZbP_3KkkK3&e{}R^EU@Hq_i)Ai)NpuA6;x#mwD-;#@;fR}RJ1B~jD@1?)VYV!Bxm zJLa`+C;7O`SK+@_x@f;z*Y_#vA$B|JFShm;q{Y>FXJhM3mPIarWn3y@?9T4&h|4~~v2 zO2}e6D|HDn#Vhq9EF!rF%hO7Am8y7g zrFO1@O8v7pjIe@uhjYum|5I{g*!?B zP1aDcWD6B*Sq&E!q?M+Mz4t<AYSni_!*Y2FL3G*ajfF2rxaHF zvu{N!Uff}_xD#sBI4xD_GgDzQ7<~4xUw<3hz32vsS49xl*R+L$#0OdN`SA&+sNgrP zz(yi7#={bwcCsqrbOts^e66-%kl4FUQ}0?$y=ye}4s9x&mB;Mqdv8V=oJKWuvth+c z4jDBAS27rUuUj+lSrJIZ%R?#|koO2OGQgWznY#dWV%I(CdqWy?Z7I}ZkQo^-g@cRE z?6r8gtw7_(%Zv~~Gr@~y)SGz6-&NMoej1U2m9i-lEUG*>n~X6dKJx2`Fp3Q&8l|S& z7(j5mi9zw$hLWydu)KJ#aSBsG8} zC1^46W0aPr7Bi<&>4L{jUgpMa+JaU3(Mq>3I4Z1!5-Z)V^X>RoOj(#aR2Ig|SGfvC zm3O?Hcb&LZ?1d4@XPPy{bwgRx?5$TM;+SZ*+d5x0j;;|gx*8&MDPEo~C4;UJo-Vib zq)7~=TNoH6>&%7Vv9N3jZ9bub2{GMlS0e7r99l&DjIH9t;}SpE`%sLE_d_nTg50xS zQk%;kt9pY-``-lea3l*b>sy@Mll2yObszt<~ zJA$<&vqWhYn5EoVOvZZ>uz=3YwEJkQkGA<}laIFeXq}HX_$Y@Jtu$Xj;;5dy%of+& zM9t9xc4Hd72>0JH7?`}eqoL-ey4g2X%-q1|7;iZk3{GCd95z6`$qi7$MU5r`EKV>8Quk|csO1d%aGI*qmNWRpFQ__*&0Z(%wwz%ZDp`=Lglic!>=WAc zBS`(~AB5m?#$X~3s;g8+XLgHs&<6ev@Nfojok91%nm-j&_govGP)`2jR_yVE^cE~o z6$uN?N-Qv7QweQabA7DHG8ehM$Cck|0QEcEo5g}+WB6-&!Qj_6C0A;mVmuZl|G}%9 zi&SbCNTp^KklOC&(lBbUkj6X-re~!3fp9fRX>K0FwRgGG@>QO+hFoA!wCGS862p5yq*8 zk*pT=vzDfQblo{juNor4Qk4vFqDHVv|KdEsvvGrpb_TKZSCwA%%F$#j)y*K5{(!;P z8NB$5rT@XwTP?k<2>;niH;OP0U^>pNC}?hBVCr~dL>=MOV*{&S$Y5|X{pcW7`gr7a z^*sZ@`|=L}5mH@R9`O7VjcioSr6k%qusSgw51cCjvP z*Y&v@)d~-p1RgR7Z9Y-OrGCKx6KthPo3}Za=>?P3cAvCh#IGJzPQa2nLyE{emN3AMF;PhdF2plz^cw6T);cbuf^| zW@>J5`E)m`AV0zooEu?SQAjNEHfe^WWQ?_*ep;h7vFRYKqRK)l8ITtnWG!iRkcyWd z)hHQ|2MaPbZ2kzE;8zXWsMSH@=Mm=21Vko2%7ejyNwh_xEHOE72Z*kJ#mgxaY-e^i zyt3nri{y#%ZiE}f%X6b-aMSpdjdvrEqNY5g5!;#lsX?|yAQdkUsbrLyGRXD_q~hfv zl?=$gJt=CJV_bi&LFUKxt8B>D-|saexea_4lzk*H!UR5f3yZL=_MrQ zTelkrS6Ii#0B^70Tq@xSbeo}KG}2Do*A8F6k}!(agS^<MOM_( zWRMo0#q_E*4yDmgt;qm8I|Qrro0M)}u(Lx6C7P$3Xdm=)gwf_6l`WaL>FuMRgSPlY z`!C!=X^cBtxafrllI5AXE?R6YnL?olbz8SBmcL6@b27bBBnzr~HZbH?#J6zNe!ErV zg%Q$ z@}tM&b$Kj{=-*mRh-5)s-USS~ZSgJU^840_U~!&e$M(gMDuzQ@qK>NYx1ScVz2-Wk ziQ;S~+QK<^g2|ofcA$cgLuH@OXpqK8H?nBTqEgI|P?Om{cygK*#$ zXLjrWV-Td4a3rMNd+x)W=TVYft*$SA_nyme-?PdvJj4;-b*Uk&m;+` zt+^UYSJ6B7lHF0m;@O~;pH}gwuxC{|xT(+92-0(%3JHTGql;Ui9I>j_ua<+so+4oZk^Q zA)BJ5e}@Sf5Xl0{P6dYC7e-4Hq$VxwOt<)GosTy7D7$cN1_=^Bi?EChQ7%v6miO-6 zWao*P8s{-h^U?Xv4mg!`X6se8;jn>Gq~Ec#I-djl?K6<-G46;1rHe;0FkM{X7tlqv z2gpm+rr^k9q8CNA;g+ ziWwW)TH;65DRD={pUf6aL_WvCfY2ShsCt@>f}e!aGaXd(m}Ds#s~WlyE)Yg&o6 z4zTChxIf`)2__II_Pa0><~%~AR@;l$eH>j?%o-$adnzJ;nwD_E?;9ZsCEOx1(+W(M zIut(r0M>ruGzLaQ?>1~W5eqwWRm|;jX9B+Ph}`dlGkQeM>E#}gYR@!F{P66H2VE2~ z|AV4N|Dg0u-6_!)bf?Mm)l4^?V1EF%KY7x#U1X@0r!zerWg<5rXN6JRqe4p1%`kvf z(H-ZE$5z(Pm`KT_&SYTiB6pG6X&tk|6DTARIVICy+jz`*M3hgqdllO5b#@jsDFQU9 zDH@?$nreB|Q<+}%#!p4WKN$q##9G>_O~>Hj`O@Q)b$qebwKE8Q?+`;3Sx zVY=PmeB&iv)R<;7cW8~p2P9P5QK770e&YwBR8~phQiuSp4ovaZ@Y}`R=I3^ih4-UB zDN>g!eNVmoeZ++i#QTUrZur68rp%O~M_shdo59!Kz>s?x-||gkd89L#bbN<&J~^R4 z2D#_Vfj%BWaw=ZyqoaIuw2#v8K4h)uevW}%Z;Z`mE>B_CG;Xw%mWV;%P>*Stk9MQE zIs%B_nSkA6D#M!m9$nLX4GPAsu^+X+dQAq_1W!7qY}Z8MT;u5asz6rBbaB^o#uE_% z)HFpna9xujOn1r1S%5p&`OB)xbxlp^n)o%=H7ztD|145Q{p==piwX%HQ`2O(sPKn% zO--(AI^=!dHBrpynwsKW6D975c$fvjg^YZTgUT?96H6k!plh0dUIP_R2-qR!j#+zk zO-jUVAIc+(eG7(qVb^4-1{z3poZKhU#F=nh*RUdZ(|O$A+(lW>Q~Oe-*1s%zS8 zAJ%?ia|UK1CT_mWT@!Ak7hgp)y_+Ldf-6SEep+I-GE%uB-HCKfMB882w8-jx%442w zlSjm-qdeI3fudcNjR>Qe1>=pNS_ZCbQasi*ZM`=oliG%XwTt~5)J{hc498EQ_ zK6Op2vTHhLl%Powph-N-^u`ykZx0 zO|#!GQkTIif+DN+wP&-+b4!9MUeOO#zi#y#B3VFLB{1Zg@Ga)3y1dAI73f`} zm?7S$WYy42@B%ZHn}K9HHkW>!w>y14+T){HAMNtdZXcz{U`1K2r=kA|rR`A{jv_uA z3F+e$1W)FB>#f5_iS!&p`eLLCO;=#WS=yjTY5AQc?EvINl>v@EGBBVz13Ev7 zt%h4hLyb9)(M(MPI&gx;p5b;SUW|@BDnugdj+!!6Q zMz_akG?Z_VSx_D*39zeZs7@HoG}o~n+!i-8(w`qifuyO)kPEdqCYNM7&=hll_{BOo zGq^5R@mLqzw}*PS&ly-3+gm3k?lSb<#pt&e`fy*R$k2MYAk^6oWM{kDmcm1>KqfgV z>TE4dHL^bLtwy@A;S4TwW2Hv0O8@FerdN$Ia(7`6OMgx2foQ?_9=9y=k zXJ(!=a}Ktszcz%y(9kA=#Nj8Yb>LC{!^oV?Rs=8z0EX5CxJQ0{_J1L#nvDOgoZ?W; z{~)JebP|VsKyO!zXjhJCSBPkrjcAvOXqSj+7msM4AJHxr(Ju0z_Ia{d$p1><@6$1_ zq`=Wx#(#{jczXmc3WCnWHQ6R6|5MsqicL}XA|@aZ;l>tX)_=FuL@&5m?@&iEQfzR6 z9vZEKuW6zSd}E931VcEt7DB<$CA?4$;!$uxzRaea>I3&-qZPUjq}Fd58fuwlhBDM1 zy>e;&-ZdhH1Z6?|gzpalTF6t})$l43 zkSHnz4iuHp3o(qq0bDqrjqhUD_+|@&@HoR{6c$S^=rRI+3&V*T5NO2}sqw&ZDoG^i z)aJuOAEpLFsBuULensf90LuI&OmvnB2h}b}1%ZbV&k;1C6)YY&)W=F9-m8&B7@VSP zjS)g-lMgNfAADogRADCBwLpBaorA7DQa9x-o7+&oR(FKBH-uH3(1jS=rozsDkRYZB z^9MTuD6Qhix|xVIxrn!qe>v zHX{fFK`=Z3(x(AHN<2BI|_n60RDN{!C)ZRHyODY zQn&X|?EW9RO=rTPq5)KFy@GEL?f%gouv22TB}lZz>Qb!~R38EYz#nNF;;f`AB;heD zvW+oUS#17h8@LRF=vXzUGm}geQiflcGK!8~n8Ma?%Ujf2ux*&jV%xAl7s59B*Fwlm zjb#eN%aPy-y9vk)UJ(tgfRYi#AT$Ev=phP#%E6c9a4ifUjp4){_>vq3Q2$^Xw(#(pWg8d|Io=Z_|J61y7*uSV z$YvW^@R0jowh_sMLq#d5*cywUio;6)5P|=*hP%$=hFKzQtwZr?2=)Ok(oncqNmoe1 z<_%;hFD|oq{>@Nu8Ca{KaRL>GPr5Oa>?T1tk>UgzSLCc;z@-Jo+s^WRMc7a~mMgD^G~L@885RI-^0aTl9H{{{qM;F=)>dRW!q zS{PoB;lv%-@QMM{KbXq0ODwTL@Dq_qYK$QHucm^*pz&qr*i0oDKPmY85Mj@RL&ech zvDE=?pw9BYXt*@tS5wKHgjI`-;wfPiWfxi8{bm%n4D4wG*^ov7?tn=CZWLpFH;SQm z*eKRZVxurd7s4p=VXu3u2qK2?1jy&GHZ|Y{5k&~A8MO^+M(whxnYfG9IN2CMcwQ|* zZ3w(Z7x?rSu7%+r*CROb{yf$bYqkQ_xByQ9fuQg=1eXZ}xCX4XFlOo>>}2W%mL!>j z2KunWzZ}B>CLFXa5D(hkk8Tips3#OOpAGGA3c_XJpe_(aDhLJlaq@QsasIBL?QgMy z-bi2ty+s#7K{E_MK?f!x>anXHsh~+{INvc7D+sj>3PSC&DTug>72vuKE654AA>fQI zaE2Dw!f+!D|5ZV31*8cCs|f^#zaiiy5a1fHn8lc>e^8J+?7$rM`Q)fh!7Tp0J2p;0 zA^a%%*EC-_f_x|hbLDB{@~>2V2?f!jY% z+%On4b?+3L#}(x!RrtqA7_UEt35SZ2P_gwJijITJz}Wt04X+~_W{I>_4Tawc19(YT z#)c8x>4A9u{Ros*0tmeD3TvKk5!U=?%F zDeplS23ZTkw_!MO2Nuv69X0;mm-(e1{_*9oBQ!Im0c+-q6xPgFbRjfTsRNpkhfx0T%9_pBY4VXv z>+Y>cq@)Z+cFHd(f=^IMu07L|;m$8&(ZDVIT`VF@IEW?vIEZBux&hnIm}-VSY^Orm5WOZ_LK)>EnYBevzk%jjS!MF+=Uf=!wC1?}A;EuXJk}C|^{Ude;P#ekS-x88Gf_cH#%16xvksc zo;YAIIL63mupdGz%nfOnX<~yUVFHKInHr+Z5iB|IxhVLDy~IBPL@2ZmHixH@i=ds> zNVqXML&`fqKk!&f z1ETvXR45mWU9>r{3L-<|vtuQVSV>b>5~yUB)ng@PSV6+w3NB1F4bMf89`SJl_$(>1L^KBpfxloF zKM9)iQ1uWhnu}EMG6X=GB;YH7HK&%|p?J_k|10102nx0z%jo>*M`&naS5 zBax{a5frUsQKW|`Qu~D>_^{?2vJD)4WB<5jYbhvd{D=yCs1TCfSS-6Q1Pfn+s9%AF zuOthzk*g$FAm_p?k^OD{&ME;yy;p@#T?nC~w|cjFjs^7O+8hb>Uo0#@ zEa;A?z+_?gna5bV8Vem86bR8G2$@F^GM`1rcO@XCi<6xYToW%Qu%^*L%|e*^Ap@N! zhB$zDnL6ygqJAZW_BDuFL`n&*2|#{Dr2O#AnULRi!Wu4v#X<^6HIJDjBv6QN18(0p zsEd=a7BWIZW%V0k^AFSoRuKy3nGTp%wJyMXFi;~fQ^!!)y^9xXanXi;fN8aBoHb>m`H?W)IRv@1?-%KBFMZ1itwcX@dZt$q6=Yc`)TMM zo4vn~_6l_Bv%i;k$7TqDBJ^)4)c%M9ome4kpf`xx=d-_$K(>ZJw)StxI;{~seZSDN zVHjB-X+8|XP>}E`s3(Su46=+^1`vWF-=<&^3#G|ngh9q?%3FH4LBd2ijE=z>!&VMn zla2+)oYJG=j5M3A^VmVR1Sd!TuayAo(Xh)R{3F8;m(_BE}9A&+|?gn?e)1j0gqfs3EIh#N%Q_ zlxhS#mVjey@Qxc)Y{Y}=Oxa?#}WL9(2;6$hg6sqb%CUxB;U4 z29oda+$RY& z0cwhz4ozhvgdCuNe)nQDnvHNu+}PuyTP7(uV8CLI*T09@0gCX#CnE9qF#<1|hNFlI z?r9?)vlHD6FIyR$fl(}}Ft@kB%V1T8dove-yqv8Z#C|aH;jI!V!hq+j3_Y%PPT0SM z=poz0hS$4v7#}y$JqfFVB^f$$$r~mQ{)trA1YCe8c_!{`Dd;i+*J93LToZ|+Aw<#8 z---^wYnos#jOO1=>||UnFOtPJxx^)#C+8w0eLod5KUOeDWMM zs0m6ZdD3GhQScpliElA}DBAuLEz)s8@C&FAgk9Y6WKV#OE--YXo5axlMG?Uf0xp=E zpwA4j0iR(Y1K)vNHmKGJeVjlph#-&bX&Zd zgiWJ`FLGMQrg3^i)XWG|D6oz-$D2aDCzucSGjc=9h+b@n2n!*0n!wIy#txw&+9*%=d6;$QpHN|6&}W>bEJ^6a&*228$mC^5$xa zfl&pQK=XK>0BNeK8+8LP#8X@Kj&s5N#VIvfSd$=#fKl?ruO#L20{XQ)35mm~mXe|YZf2#=} zt3ow>_kXA0MeQIg#Ri9wxatM>=m+<&4lY}bcEFYc3GqNmDv%MO7iW6tQrXiCb_^4yjlPWtauJ01mYHC;WFwF_KYwrT7bSq!tN~6|-Oe+B2BXB7e8@!ly9e22=Z4HAO*LAOa82Jj_eb+$6-X zhY$bVBRvH2uP<{YM!69aiYr@sP_<$)?ZNz98ZeUO-s%Jo6`Sm#N=`kQjnAkp~ zXC!`bMW_xs4uh0Ics4Xq!9Yrx&L}Q4C14V9sG|od>DL}fjPSt)cvKIL!Q*W(0}pEq zP6-^!#o=_YibX`u3lTs(q5y=y@47b_0Bhtz-4z4|+N?&U;7SHxiOvoA3iAGC@`@s= zP!2!)fT$C)!(3um#x24-A~EO0{DT-D?`VRCQEV3bHEKUws1pSWNMsO&1M@Sf2Eo*m zE0$5$*)5cU4vt&}um65DZ-F%^kODxIM58j9bcG~rhe3B` zSa(&JNpj1sZ}AM|(6)m|(}QOr0ENjYDg@J+WL|8@Dd;j{F`SL$`RmSIqAoT!cp?Vy zk-NG_?&cr4C!dT_k$cTKmrp|t;!WfEz#*6k#%eW)u(FBALy(jy?Bk*op^_~F{qP=6 zJmEy_lk+L1P=tdyokb#I&Zltt@AD}P2I=T};l9hRm>Ot})ez|-V%QrpDF$LINEQ1u zL!%HX0Tt(s;DOga1aU|h+9Z@jaTNU7)dD`OhHKN&g@|NbMJ6^v1jb}lUW|nrfkXke z>JW@rMvv3}xPO{KjTnyM4Z_RDevKFqO$1T9QDemFm`$GVk09jCXLor>nBq_n7=zot zKa@q!?(&e44ebW%2~NpJ^r67~Fs2a#b4^2YJ9cGTkyS~hT8to~o=HUgKZt<-R^GA) z5rGk#bmJ8}ORLBuA4g$=;W>j0nyUnRG~q7I=8!`Sn*c`(VV6bKzq(_-VRsy2(t{cs z4(=v`E?MYZe{+DpO-Vv9n|Rm_B$9s_b}isse2YjwHZ$bt8o?02u83#Da4o(T}tBALL$V{{pDS|=WR2L^$M2x^Rg`(QZFIimjg-|CxVxFNyZh?=?>GIAUnF=HSs zdltb{4{%Bjf?o~;m+(>oxC7%L>cF5K)Oq11h2V+{T^RV_$nb#J_Hnqk2qBy!W2wYk zFXb%m;MlpdS-%A_Aw$v@Mpe-?jgE$BbZ0h3#%T_5NMc&U;L$;1AVv}YPl&t`x`SyD z(^ZH-U1Ekay@xr&K{&+_PU6tOc$8#tY7tD=6HK$KgM6VVc>sghx-!i4XZWTeNhh&d zB`t!OZWByj!S9PlD(E*lObR-)mwp)VzpmLiC%Qfq_@}Bb{gl z7KGf^epG>w*!@x1Tz0Za42>92=%hc`_6V_l_JOj*&<2<)n9af9@Ym*G;#=M_iJ;Afgt`QSv+fWk2+N+qIh)8~dBT%JkRuonECtC?Uyt?B zWLXCc&I4VbA|f^sfeONOA&$KIkXPok0pVJNiZD2@0}Nz<8fi&{x32&Okk)T!r#rb3 zLQI4Ns)%M;!O&$;$KdtHb1(wB1fck>(tx{R6zUb_Ev}tdJ*ZVlHgF{gUdIAUv4M+7 zupA34%?7?qf~T;+XtKm?JAnlEA#YERCJW#q_@lIBv@@LzqKTYjd^j*H3CtdB+ko%5 zquB;LM+hz>YVL;fYONo@CjkWJn9SYTR3FmaJ%i`KWOItp5(vTcARAiF=kSOc#+euR zvfl@$AYj0F3L*(Q^egt*q!+?@hbUC`dm%1d2wn<@tUQD>lPGxM_kyp1LqFK@iN00S z>I(xs1ajWmriSRuTzD8I>h8oeG*xs^xP;}BZkVnA8mBGCq`)g4Gl!?ZFuh4-`J z<9rQIvA}d1@udK>9dZq`O&HKC^q$3$)UoTp11y=qz6`THVyYSP2YeD}Ydt%L;r*_N zL~>-hvX_lS;&BT(N+4VMiz7wj3kCz@CAQc#vTUU_7~)b)QZ*2%49=;)2o^;g;0y39 zd(^}%alRbGvd7CvPQ)C91MF-BC-4G#XoM7p7Stu|G5Q0$J+8nwQUCv=JvyESH8!AC z4`ChC!8%0Aut+X32Vi3=n^+#9NhhF1O;2YfDXb(HD~Y5`Wzv9_uD<&lSsd$P2xlAY zD`ED)!XLvN^S1uRF?Jb*0jz|&1jpyzvvaJ+Z0*k+??ETe&SB4ke4i0J$MH)*jlslZ zmN}^&;Q-80J)(Ji+zb%~B_E;@6R}4i9zcS`0sRY!;uo#W#z>e!PTW<%# zc}$!$(5a>b(F=hrW%+W#(` z!RbHGA(89DVBnhEKix50)Wc%mEEyXvF>m6axcSNf=e< zq+g6GqGdF2LoNcy$J{MLvwse?z~pU+d1C|c&P+dt`z;`QCQ#3}Lo|5*ch(a!^o*2q zHgsEf%pA^C2Ioo0S}l^jScR;}Swzcaco#;?%tp*8(95Ogdz-5i zs{Qx(UkUtI0{@l3ecyrJ`Ru8mhV!GpZKK%H9Y^FNYfFdo2>;bk%@O&Iq#nunL~GI>?rzeK zE>;$f(vJ3)($4ly`0DQMPNz9ZTe#WN?VMp|TOZWdOw^)wqDZ|DD9-^I;}Sx<*%1-10uS6jH#wY{wz?X56|f46Tz)+cyG z|7B!kWM$-JeLR73G!WmE~3BRTX3uWEJETKUN~lTl&*bwv+#SC=g?l>8&giTw9L;Muk=;DD*fz*)(xQ+ky)O? zp`$%`d}GR|&)eC3{qyPC`7Sy&)$jIvn|(pYLEN2lmFJzEzLu38C*?c1wZGZETT{E6 zI&YApr9?C3;nA;Jr^Jpt+1Tkl>Rd11$M%8>TFa9)uP6wr+YaBPvQ2R_|!qG zJ91;;hT4r~4sj3WhTkun?4Z=qnRcWjH=wX+iK>QB`>1nQqtl=E%1y2Nx_8{#cS*iS z8n177a9QKogU`?Jifs#@cfqv9e&|#=z9&p%#kZ|{JZ5>|J)Nyvcp`mjXCHsY?^hZS{gd0PYN6pV zXUlhSlA1&AW|a~A6_Rt*xd+1z3I!ds8}K+N&d%L=c9d$up*6SLZw-kE ztqIK3S*aa(caG&Xn=3;L&x@ti3Cx(L3^qou^&55Txbuqd_jTycrBr9^ zv6`Bes`uPqn13L{MA7@2Rj#7Pz6G(bO%nV1st>NA+?=*q!x;q3tb{~mJh`Kj>Mn~|&feA`x{8Kvz z^mF&wh}d@ce5>^9o3=UCTc~-K<_DjJZ*mXUm|j#K|3Ef;a!|zOypAi6#&sX}pP=#c z%Cz+Cm+w z&Oz>9TL(<_>|!H6Z$0?@Z17Gak3`i=k{sLjcpncB8WX!>{oS#CPF{yT*Vi4qn(nvs zaq?cS=8C6pkQsJ?n|9 zLU(VlJLhz+;pP7O+&}hLnMp^djhZlig1`o|pN9L)v=x_rd;BK3c>!a?z?sWVGYvTI z?7e-0@2uB2a!x1~KPd|pnrGW@wd(HZ8Y_;{ex2`ik-Ifd&aN_BVfCuf zTWRBau*x)(*#ey{C?8pQ`Fwa-1_o z)@>nIfs9|l24COBUPEO@1#4stI+iteEV7=bxUr}|QR&h4i&J!YqfZnny@~Oh73k;x z<#?Rnlb?j zA?apzy0L@Do^hSLgH9jI4J+H<#E6&iZ`N;lT+sC8RFAnl!|d6Oj5^6()*Yu6ZZA>w zxtv{<9&6~$D6qYw_1SY}{L2Jilkf3$Jssw=^uMJqX$q#gT|0d{U|OH5<;mPyWwXqu zm11{1#D!$b)eg_U?@zA_J?x-#RL*MuP3v7>&pb{Ih?5;X>eP_g#r6WjYXMxwsXu=B zj`BVD`I(Jmf@SJG+nRkp=9OB#cB?kOnW&!j%E#Av_Sem4?yp?o)y~5(o@S9a{yn|N zzwp`mjW+9CTef;X3>ArTTqqS``{lKq?yhGJKDov(&esd&Q+kgT9eY?65T7Jo^I%e9 zi%-w~OFxQzQ!6U3jP>DvC}!bkHcLG#v1Z`#4ldD6J`UbiGiA%Vl1oozE#;&u$-KM& z-MqjvJ)*Dl^pUZXb-uqSTzWlIpf>wsA5~IMz+^D0>rQWoBJ=t71MSF4+c!i+%xHHfKN<NBQPI$1{+?O6)mw7h8v?i`~^<52DmBdF9V;{`j;ORRQS>3Qu_xxC^yW`JW@z5@eYM?HWo3xKgp0aQ z^s_>&Fuo?cjbS1_^aNos#!U9%5&UDE32o5GCI+>gJc8H&t# zxVq}>b_trR--AH<6H!}xifHM|-AgsfYFBEE-?sSuV&um=tNcp0bP2 zuJ1vK$@S9@ysNHt*=RDR9^O@Sy!Pl@k<3G~CYpNnt3Q@WQ|_z0SSWaI|4ySVlj1Mr zEiL{rw&vrhd&2%rJ(IbfB^$^T1nElY{IE_IcA91o9wD`Sw?M}!gEK>ca%0wnZ9b9z z@bS!rIt_PzF4FLS^RZh~{8rE#nV1b<_xFXp-`pkf=(^YE(B%!$C#pfRO4pW@t}_-Cl=l>eXsMfIX6Xs*ZgyAesTYHHNK2RuaYLe z;Lp2qv!y5Ji{29f`bOTTk%Bg_+r=(eD{R%DH96+){2P^L9gZq?6jGk?WZfHF+HGGF zsGFyq(xtg}otgRox5JpLi%j27S+{ud`K0WLFW;BE<(>I8Jku?|&HqEs%A@h_FOD|$ zUERcMPqUrVxj8S$s3vdvr$$NR(&e}8?PT0d+_#+I%M_R9^E3(XI4SUdUip?%qBGH5h8 z4e2!JvFA+O&YiRfcv|@L=cK*H+!g*FfmDtkQv#HC>}Xv=A6xKYeQT`N_qY^J<=nX{ zll7Lwr=0)3Zt1~CWn+?-1PE;TdRyjzcFM*%9^8Ft8NS#yB^fn_5Aqb@k!nvA1!LnDkOJzhP37U ztT=4E&pH2I`K@q=R;eWGpL?zBW^vmWe9Lk#JHMy$hq~nQIqQ-wrkz^LwJmvKLgCmv zhJ7>dz^qa$?%t}k`p$X_K9;^Z+W)q9@IiO!`4_=ka(AmaY(xf!WP<@v$w_g)6@0i(d9Le<;@-FmHRLG{5jm`qWD9JfETT)C&(`>YF{F!MJzT_eV~wgL~H1 zeObx<;W7pBO*gIg=~g$qeqmPqH56Xz2l0?ceR8V>~rMYVHxPIf5Ey=Xys%{vyfyJQW6Pk6}SzWK*h_xY6kZm6y^UF`O{@@HP>?4QxvNsEGLUD<1n zl`LPFmaM)fx>COF=ORlnb@#x$ML{+Xgbtan*VwXOaV%Hr0)@psLskj>3#u-^EE#ui zz055I^P!t}b9zQK9K9O+-FDwcEftL=-`{0wKk*XHyiwe#w8%H*(Tj2*2fjXi#R9hDsL>aB-a-GDk{c&#Cc3(WEqI-5eBFW9 z=;7-7R~Cq*jJt5GIyFUiN+Lz{fV7u;#k>dG58oA^7-~a#61H?>#DL_%FXsm3c%^+P z!EW~)6uiI9&c2^NRHGKJDY5ouz@fU_1qnaXiURd3_`bYV>bv}2E^pr1dv$%1`|myD z(Uvuf*LdGqc9X}=TS(m~GI~EfsA@-AzeHr^F{{;y9{VcwuDy9-fAYr6H_Hnz1>BuA z>ZI87^*SZ?7RM_uY;74lSE7{>VO#s@_H~UF$KBozB+VG_kZ*qH*sLeEch3ese93Em zpR+YDr(n+ATQ>q*DI2D!EMGa-c3!9b(ktn$b2%1Gco#5ncd=5m80`|bTTcA9%9E+# zM_qNU?_9o>F{z2GzoaC}Jufuo1LveGGW^05a;{%rs@LmT5K*ekA1xP`M`ytE1HccjWT^*adg7WMaV9ieQVWqtol*sMFxYr2#qLozj` z#ncbzy1n&`3{3ayzfi|_W9I|m`bk^c@}t|<-*^zvmR)j|e`RZ2Qmbmx!nT8{^DVEg ztc#ngG08e8EUW2k%|*5N89WC&!h)9zeCz5u?|Wh17#X`N;~5fRTXNIeLKEvHIhU;{ zP2G_rD@!w(fBE4W@3Hs!`=XR57cJjmA{gfLXhKtANd1ksPn{>_EnT+BQFBJ8 z?Ug69H!ELJ=`lI4EETz#Cmva+c33FD9A+Wxu@1sqXRWj?)Jy$%}Z~) zUS0cWLUTi)XLV_jV?@^RlNp>k~*iqvhDk>y=uwT%0u;)u7S~2Z9cN!r*8kcW5v0!QHE*W zzVj27FFm37`D@KRS?%~KrcKV{dG1ksKV1v_@xwT1y7TFKA$kWIBXb|o&m2%Nsj@z| z-Shcr)7|sO@|T@Cw0QJS1ErXCce);542=ECDfZd7putyd{ZbE|hf@nrXe)ZV#qNx{ zz3@m>4CRE=t?0voyvsOqJ+hX3&G(_){itfyz#mdJ&$w&vbGlKy@Ub!5@3l|+{^fDo z%*j#KT+KKAKi^Ciy3rSuWD%3I-(YsfdpXDIcdfD#gOTKz2Zvf9^u!V#0XzpUa=KF!NpziqtY z%X!%`p}l8r`S}@7nUuY1#T2PG`7W2oeoCT=-w!f!8UNsB=o6}xPHv#ax1Z_r?oSwM zzocKMDv)F!{idTX*aXi$D0m$?4&rPoL=fYr{DTC0;gd$2+$Cn4-J0!r1b6 zq;8(eV&yeqMmOOVE8h}4W^Os0)?)rFGw-x}U0~)YK6?an7u3}c=OZ$m<-7vm5E1#y z-o&jj%`T0Zr6hDdsGL>un3<3Fo2qHCZnGaVUy(E;;(C(Ha133ISF~?o`29*I+;4nz zzn7}UAu_MoJmI=h-MH`sGH>*1jqR9+cR$@F^NF!yhp3wST)vZe!_(JXB~mZkIZfn6 zdpO^%R^p$be22)3JC@CAEZz9p?+cNaE8jNRQ7v}DloL3wd2rcPfpK^DRvs(HdA_r& zL|^FLm|0TLgY!=UX7>g0Tr)bpI~L`Abf!*`Z8v(Gs4?{>%Ey~L6Z|p1d1lz!8SN;~ zl~H9@s9aT@_ozDx<(uy%Y`MTWnsZ@UMlNJE&!`)GoKdb_b~!h;8S>Sf4^KI{Y-`(F zDR&?o^5^DK4mXALHqAU-WOfx=JF>**T!XXBnFLE|#YPH6^n$YJgj(vWQAwxO=fLq( zg$H7N3#d7T0OhkZLH4w8?6jDDDgab zFT2=rQ3tf_L)4es!gsM(iib68JzD>zRRI#*^!9SgF*|Z_3r7Ws;<%P4%w*oylPi{ z=dsLd_uut|cpU4fsMZc!(^?>SzEG&MYrXoy!P`${&slXgtm?8#O;1_fFEluB2X%Y& z&Tjhhw0kY#N=>R=IZr*V>qzj28UJ{DRmZF%&3g6Y57TmDcctEZVQ!!%+*Zij6!B2@ zr*5lB@Xdyx#KenJgs*887Q|U)WH?OCcxZUS)cWls$<71IVq?oXj>Nl~=N@33TehL9 zS$OvM)C(1%DzsOZ@2^^N za+Toe-KR9ft$ErlPYCZ?@lDw5cC_zWxq|AXr4$;^QBNBEv$yrT106cy_e6KJ+}K%U zcl`eBHC0nWjvr{*{dOm{yQJtq@|@E8PK`6?rk=brE#qqUY`(VMHLX>5nmc@!b{SO1 z@Ef?9KKht&Lqy!-UYeQCjbksvE>fM3EMDODZP`>0fwqF9ax?43OMW)}Fe`b9ydm<-&(g*mM_HR+3MT4^1_duq^WLWhR(iEDAM!!oxQ z1Zx${Tlsqpy*lhe=pjNM${(aLb0Y2C%~JSO^qsGiHDmMf+K5kD2Qf*M{PoXX%F zFZ-%1MPd2jb?E|6&Tj6}T~!d<_UP8bx6Myx-W}(fIsJ^|8M(Q~i!bo{Uv3yv=1}!^ zGJRb!e;4-Ie)A zl{vDM9OYfg1bgPbx$!JD&ri!tXC-Aqo^Xfbv&DRm-#16n1vVG+Bu7nYDxcY=9U=X~ z?t*e$MWE;O2JeKX4cUW}OXH}8`HP~rZ>Ya3yz|YTr@cR)IxXrGzM-)2%=)*X6Kza) zBsU$O-qgyGMg1`Ia`-{1uT^Hn+s&+32n#06dFd`P(5LO;x;y`XzT3qyF~O zkjp*%Ny+Y0%Iv9icAlAzVMEq)FX@cxezf4(MD3WmadILeyE}JUDEj7&Ytb)wuXFH2 z^5fJy*GDBLkGDMKC^~INLIl?oWxLT2c|-S<2ByAHvfk3{AT~$LC-~Ts=Nt6(OMYB_ zHP#_*alB(eE?@e|7oXJ4Vk_<#D^`?eHb+Mvj&cd@yP=kwx?3y=J!Z(S!Q@A?)k*dDs_(RKb-v%GYUjSrRgbn)js5X;VpRJ+Q3 z%cRjVGhOigL5YNEIpfU@WgX)7Zi+trddF`4Q|~6^#sBa>u%&ZEnH)-(R6QiiM--DYo;E!*?Z!5v7t@vZ7C*?#$KD4R)0UR!DDFJ z(N96OE9QMn8DCl0y1}^UWv+Dl5o4#$$rBGr&08$q{kVA`$tktMZdy3s>5VhJJrcuj z2I|=F=WzaJqSIks7sa@mnHE#}^3Yx7ohA!a8DCu+1*h)qGk$x+-!djoJMV;UXjsPs zFS=9A(Hr^68*H>pFI`zkIrgYxkIGK_>eixlQZLTP7Juj5dwlF$EwN96z2AdIi}V+~ z>k!e|Qj^}*AiXT3biwWmLM;nxKL(iWtX(?cDgE-}`nhj+9ob0tu%vO8`|Gdqv)w2e z_{H%N|Bb>jm6R72G>*)E@$7Q{I(z5-v5(g07jo1o$-g;ub-^g=*2Kf^3W658ql!EH z%rCE)=4N<4g2U`v-Kd_0NnYC?pR5#p(5IFCl`*NzL`Qxm@ZL6g=7f)YHpE$+ZRjBJ+r~7Mfm!4OalRtzkFMIUF zKRQ(+le+PK!;v>qmC9ocQm#iS3D!w0k8wJlf9p=5(Bu~7F%1uL7y}jYQ;)xRlEoc) zPK;YKD(;kU#pZ+j2HOpV8FiHw@& z;F_NuZQCxG@ok(M#*?*j$CAM1sq^X6J)#|se>7^C;rUg34Zrjw>ATC`4aKfM;*x&i znX5y=;k|%XzZLRM-QlvNTu>UHi{q5KUwe9Y{RBV(O`)-U%4^58Qh%) zN6pQ0>>P5M5;gnFN97iIMCF3dS*`$@j>)iRxw#Ao|X4rZO-&(W^}XTQsT>h* z_5PT!Z`J~QmkJDcbrMrnuiky*qjKTQCo`Q+PcdOU+VR@Bt@Do4oM4Lzza=L9Z%;bV zqq0vnWq)4wL(X$&&WpnVlXDYFgvI1TZ)yl|CPvL|sckZlPioV+l))?Mo4j;d7H9Y* z&6H1phW8is&yBdZRk-{LKi|6UR?+43FG~~(t~7bQp0;7W!@M-Z$eE`!bGpMaLc%tD zEy)S4o+(;deLll2_F~qeYwq(h{SsPwZ-;K=UG{u7{al1(UGv>t(+34(gK0rgL9{*l zlBEwYUVS?~*nS{Ax9w-#tcV*8vKzhp2a;#k(vp@R6r(RTd8O!Zbep)m|Bi)$;W0sK zwHrt4=DB}5n5_KKt~Y;i@R22co09puMXLQJwJ+OyNJyT%^2K)N=P8HXzb~3(xoGLB z6+8Mk?tF?n7rr+;GQU^fcZ%n(6}zQ1?z^9uqmwi2m)a;xXOm57D3(2uH@pO&R?7<}poU1qDg)aoY3J;4Aj zE$3rX>iru>a~9SfzIi&u;E{g8^ff8+ac0h=Nl!oYH&B=Mu0Ndd z=IH2aEABQ&+_)#meffjswC8!+mPeP0chC?-EXlo z;?BGf8y|_mh0j~HVqbpq%l^_S>0|!oTh==3nj6L{$#2~4Rpa~Jy|!ODU}d84ZXBcK z&0OofdJ|sk*nV>-$4mXomd}@{pO{%0`PIYW4u|o!mxFv>4>S|BcIPj$n)T-D_2ARo z_YYQ#X-!+yx76~PsCcH%!-BH|RI3g0AMfS!E|RN@(Y&zITX0`m@zcU5X`2rQnCU%u zkP+6Y?;6^rKUGE~mA+-DXyYflZK2XTx6Syzpk}h{kV}V@uY0ap$+K`XQ{RjOLwAgS z$X3k%ELYu2mr69*elRz6-|~EyD;l?D_|+b^EAD8nnN=z7UzM_F`zjrutB1B$Sv~Uc zwkcgVLutE7yojY-=}pczb(#vvXG^c`tVkL7*(KIKYPB!t<9>b zzy5@>{phL3@@F@mD0<{PZf(o>LyCE&6_k}7KGFf)6_%E(->gXP8z?qiXmMlV!-D+< zRS#M}#GUdwmM2uJ;WR*49PObZq?oz;yckiLXbk z-?KiZqFsC9Y#(LbA62ow@298a=fB?&KHr@4@ekj9^F&`8wc3j=?v=_bTX~@7;WTywgp?hjg|}pRx_p z+ufGZHlBBryT((+rM?_jo=<6BM|)7SWNp=|(cDdkUOh71o_yfoxc*2B-W35-lS2-F z9awnj(wM<7sz+-U?y0?Cb?w{76EE&~)45d2v+50=-4ye>cxb)ybUjXAA(!&Xz-s~% zcp{IAg`X%p|IWa>Bu(svp^=A5`xULc&{L-c<)5^#vq+2@o4iB3x9(6$*I7UL$2XH_ z-7bI8+ifW3-d3JO;c|=+yLO$w+u=%Z%g(hjyKHngER7!&B>awG?(cc zPz)I{PMxFax^kL|r}~a@=rte|t@vmAv>qikC1onWxq1qPNAynfvqc-qH*}--MNugB zV{Z*aF7>?Sc~>80DFT(7X+~~Y3z}|C!udxfuP&s$3w{#aOYD?1V=M{2VcrrUd<=sr z-fLZE6?sb;sm>?Cjwma3U+Gf-c5(_BM2sU~#5Mv3E!KY~3szsm|7}F7_Y^ zC}D(lBzv;~4ed!F8%Y#vg1FT*XIr`*#e?o*p`v4NYfpDKcCdGajmY+n_Rh9jXl^d< z6cCa#on(e6v%MS4@8U{x z1}Z2NH%l7D6a7iRKR5Kx0wvJiW2!zJe*FjE?%vK;_AXdf`fI6fZZ2+s?{DR?z2LG| zb~GyoC{Cf+(&)NCw;uQopx(*Fnx^lrWA8>m!~qGO2xx4-hen|}Td%c2If@(2!dlDG zkuW1>jvYd=a&&Pg%Hi9D@%=?QK<_U<&0q4vSz#ijxyA*g4n0Jaj`s6gqLjsM=uj5m zyWmANfBlJ(_3<$PTut1`#hotdMzaDZCkmd_QPdH(_%Hek5`MD2%Wj&Rq^OIVDD)tj z8*~mhPmX?5iHj!q9}k+Siw9lQ#YWW4!r7LF)l6a9pD0+5y|XA~3|U@ON=g(ZCCI!z zp!H(bw?zHaeTgXDjwT9znr>n5Omh>pcNe8Oxq=sB|B=IlCQ%>8ClViOPZS!IVKzio zV%obnnP<{3sS1Y=Z2L8W7uwI@*IQpr2TP zYEX@^lSPwUMXuut4 zfkQ$T)>uIzWc$YYEA`iGP!mOix0{=XD=1Oa(pyxU?&c_|O{kJ6`OANGR()gY%8h`) z(UE3r;RxQw1?EdMQ41?8n!CG{sJIpw26R6}7V?HzjiS!>jv9Y+9T+nkeOfEl1A$tW z&SF^&Ocs5*MpURid@@QD_;hjmM^#*N7OTGg>eVdvz}LeJ;@@qf zs>@jQSC|;H>i=I74`mt)zmYy-(8%5uoE{oXu!4C>xQDZYvx}#*D4La8II{YR015wt z|HK`GY5!7EL_xBqCh3o5nD+@N%bzSHZ!rBeOqKCK&q=mtV*v-vAWh;QzB2JJukgJq zj#2O%P5#FG^5gsuzp?)>f8+jt{tPkG;;I==T!Tu9>&||B_3@L^qQI}3 z-wI#->W<+n?cqao72Od(Y6V$&KePH@$1J|%Mwt`W0u-+T&Jg$17-CTdfB z8kN0EwX`Ym_5Pa_c572A*4R4T%Fw2$N18X8zR}hUz2+r+NkGS_`|Z=FnX7c7Y7NI$ z_4w$xLf_Z2wsr&j`1jBGl{9!W7vkigy}L1VZbxl9Fj$H!-3`y99PQodfDFx^H@FzX zaGL_2&zMGENuyK1qdQViyZUG@fEE+l9&T=iG$)FSyABOEVFb|sll6g% zJBuP4Xi3k*mPU~DzdL_PPD0#a0i=cfp*>nMS|KJ)oIRkcQk>i^VXf$7;pz(O1bt_8 z=nkfYGz&Mhgmj}h!TbpmpbLYap#`iaywIUe4_9kgnHss#c4Il=0dlPeeXR}R`u{-g zPEOYL?yh)xOHRF$G?z zKY|TjuxNZi`ih3HKk$8C_j|QxpaHPW)WrhM1IA?HS}K3V$h-_8({nJwTX~twre`ij z4`^_GoN6G;#60y~UHvUxowdI2?)tt$+F;(kvG0#g!+F0t+vnFu3vos>DhF2|mih~m z6F6I#S2Q|52Nz+gQ(&Vy8$txNAq#P3pP#3@bHnS?*SxOxr9nsWb`R`SnYnYNvz(z$ zvRFy9$kfbb)8;O;oRNequKLt?f~%gfa_aa;?b{baQbbPzWEUcz+D19)D@Lm;Nqw!# z+*X0QVp$Tg3f;kPq}qkn2#g&^d;Fv>H=;Q_S8k#WoHzu zaCS+(*Ww(|Q$in|keMEtoQ-j{Aan5&bew60&JI}U*=6e=YWk~7UfBoL-<~mA%@Sqi z6=E0~jdj4zS7@o>`V42ta<-{jN3>f88UWk6(3;B~-@As$85?7#`Pio$?sx9?cY5~WR^gJl*gsU4)*EzYp3}Rq+oDy$Vp^(D4MaF96zNHmhVvs? zZhBr(PNtK8Ty|zoMy$__%FoZi;R(yjN-xX;VkEcDovk}=!+EhSvvHd2WMxNpK#u!0 z&uR9MO}toK$LT-9&YGyp49vaZS{I{vVRi;CsON`p<%Vkj=aL%Uh+5-6o*#-VDah>W zc$XAq=je(tw{v`Pb{FTG%9^FF-1VQ>a85mN5uq2vrgh8e5C2EbPqXuMJuz0K7ddC1 z+9%h4qO%enhvN%N*8j=+VQa?L&a)a$4pr5H%t&UsuJIpiKlFR=eBwgPds;}3nL^CE z6ciPniAy)9ia~iWA1Cv^Dh}U&*PS6wuUMF;->Pd(&0HS%J$!DrqAYchOh$Uf$RF ze-0*Zmn@%?o|`dvpi{FmXN)~#3X)i7jy-GQjOjB*&X_sfoe$`%7lShhHRg?7UX+4K zUwne<=V`zE!U)!-P`|+<$Y1|Fuxd3dtAXXj+Cg8JtI0YQp)X3=j+pt@W2!Ti=L`XV zP!G@kulkPK7|uHoq+p%G3+)E;IL(TobZp*Id4ocbAMKTyw{)pY8sp>{gLWYT)$p#s z{|QS`=F4VGNf|To49lI5n=y7uit@;O40I<%=I@lQ0_1wIZ&5C0(Jkwg41EUUnDJjw zePiv8`pv*Zf|_t`!0+o1IIGUc=JYrh)ZlVr=Mj1*WK4bV|JD2>#-#LI=LoB75mbMy z_q^N;>}vWD<%Ho-79)8c6@2w@4lCEi0z$W1IB%d5iQx7PnZ{2l3bn>V&}fG}mS5r)92KmS5mj zm_ASZirl$siT+QlKF-y!74y%+$(D0#!xs>%@04_B2h?g{(h0<~|^ODkg zg-$soG&#+=TLc2Pp4Dlyx_5+EVR8CBBeX0FM|#Y{JJY`C*6M~9id;<>Wc@>Aq-@^r zIM>j5nc(J;Kf?MCX}?H1j${8x`$>H+-~!^A-@HF_A$99-&0kS-UJbXeGpXTu-xw~f zaBtpt|Cbuhv*$5y!}(-h=8u=F=S3`Q_lP>%`|s^j>g;2|W#Zo^{(A<9Ni#KD^9So= z&hub>oExB6Bh-J%s5(37UoU9t-!%L~ly7EbUd}>2NFztzto+=}zUOBz%Ex=#3L~)g zEi9PVR}G;0Cyd%4mE+n1Z~`SkG}MGht@Fdzk_l#0XZHo zUXovw`MYRMe09L8!Cxiy$?Y{69;@zb>x;FPoWQL{Y zOMXES?po_9q2B7)?+jS=M~8)$`KfN9Gcfy^otqiLr3&s1%l^RQepp>$;I=G>=A=BF z#SK$S!#L1uSSTIOy5SUAv67gWtkI3<2p*82>#(k&9=zpm0|<)JT@uKd?vqXV*_Hm@js+lO&7c68&H-|3ALIlK#<^ zJHC|XoUWJamPhHb9RKHF{u9$_?tH?Wl*yP4jrH$2V^}koIZ2fjIoZz_aCnV!hdqSS4Wtn{zLlz$?GR)eET0=LV32IGgO7ooh_G6zIsa` zr2hyjGlpI~SCqR1f6c{%M@ zgyH9NKkoC3&hyGSd6sN{8Ledd<=}>t`iP#bE;KyljC@^|{vzj#eC$0OQup+-abruJ z{o%O*v_bz+dclGv-jg#=KABIJ^$F?pzx$u3`56B(HItK9&tm$yi&4Q+KXubCgtDxU zTwf}W_Y=~EFvjm#?#$)s^NR2kTfJ-GjkYW~eyezO`25}G_uJk1XSBDoXlehT2`X8C zb$p3cP%Wu<$eCQlW2Ttw49(5Y@IP3}<;wYq6aQx_Xw+ApUv%R20zIqv`wI~x+Xa5D+>=@@U$!EZ}h&{ z{`vd(`g)(geN>yYWc^ip)wj+v{;B=h9Rla7`{{WLFcvnjXX?bAm|w-aVX;HVAMRjs ze*F9Rtn{Usq0FVY9?x?whSZ!xW)2=YiCrvX*|NXQRgZAUq_3`%^M9A$*Y8h_b8ZS8 znApc$%KL}hFW~+Z@@ndDS-))0@QkN%GMw23=L!2fJpkrA4^!j0+)VvoClkr|ig<^S zQp!r={KirMccT`rOBNi@+sH;(2%Hpx{Oy`k$_d!a3b*8{2qmxznPq~!*?-`*oM#h{MX~^kt11rgK^G0gmyOQ zi_IfA5$;oxm;+dllQ~sg4F}AZuu=4a*56%cUwY5)|4s35|AeRIX6-o@fA@zrk>&bg zgScN2vl{=N!@t?c^Y1+minHk7&yWA#ozHS7eKF6YT3$bIrv93aIo0g+9A|Q7tnR3K zw(C!Bcg`OL&Kt(&#n7GloNC*jJYHujJofS+b8#6R`-Zs6fh>c3Sv z7k}S`f2zXxdZx20WsJA&v3ify+(!?s7wu(squDLX;v!@D3zp=0bBBKaVfCGZC)}RO*7GN? zdp!BoHJGRJzt5kd9zk=mI~Nt9k<<01s*_#L@7N6P^{iAx=Bw4*pbC)j)jUs=0& z(<_ICreqg7H>I7)|Bz~8F%so?g|>DWBOYI)Q(v1b#Q%6;Qw`5Xyc~br*~npjhn&yM zSs>Gup1~5itnwIDOkN$oP`ki`s#cd^AR67^u4UT8d zCiEX!8}#?}A6K34daB1|P`RKuJlEs--fFPVc%H_o8TTdUVYGnbsUCbhT`|BH8;?05 z(m;LXc}Cy!c-W71J?g*z=Y5VR7w0nS=B2YM#vRk>A%`@E^cR;Mc$!;Fi-7 zj|0b7z+~VFXCNMU2p9n#KOOPFMZj|4&>4saE(F#9?*%5{i(5y|L_BajFa@{-7y*6? zECr4@6Y;9BfzoeARf3KSPmS2F5-a;fHlA` zfC;#nS*#>1uz2K z3oHe$NJBiZMLObv_X2BxpJgB(1M~7s!~>6Wj{57s9gelmEZqaM0t32hIjsA#8VG2>22(44m`?;(_-7OB4?* z1D?1A@xW4GHE`gQi0@)qZv#WXm!3jA@am@#4_vtw@xUrz8F0ljhzEWPtOjm>7V)?o z9P}LGfyY)L9=HOS28??i@xXb>^2c5Dz^4MZ^PN0$SaW|0To&C%%k$V7FHg5BwXj z1lW0d)ZPI63b++`)~ivw3iuK*3j7+Fh>69$ucQ5eAM8N;1Iu@!{efNHL_F|X;8x&C zyATf?yBqPqHt!<-ILrDHI2?G&2Z#rj0gHi^Rfq>3`61$g!+=%5P9Gy4mr2!9YQ?tre6>b+y$%xp7ATz7zMr*Uu!4g210z}T6;L~*e11hDsTj_7})YK!~=UYM?7%j z5r_v~(E{4vh#~~hA089m@9gle6Wxx%< zwF40k>@o=Pz-_=Nu-Rb5s?Xv70o0=ELU0jq#%V-OGQIkwhr-w)#$a44|LxLW%x;77pa zz$p`I?e)M1fLnkwC)L_}fIk2a0q35E`2Lo4-4w(F+fPS4@OI#G;DDKk2R?cx;(%c?6>GKgk5cSVO zJTN00@xV>M<-pGtA|5z02l2ppz&*gpd02nolfcA57}q0cKi~tfyKc61!zCu zafN6<;LAm5KVa4p!~@qXMf_mP`fVBFft#;JJaF7P!~^GEhj`$^>k$v^a|7ampRY$e z@cA1NKLp2f;BergTM!TY2v`iXZbdwBGjJ<#`)!B^&fSQ3VAoBEAByuYU>NuVFb%ly z&RV+!SO+Wv9(#AKT>&iHTx(aW@;$Y-brQ~ffg#`nz%cL)U>b1MKM)Vx1S|t?xfk)k z%kM)xaM>e>ABJ{)6!E|#9z#5^^W%sIo((Jk{uNjTZ1Du*fhPj1f%gHelQBL3L%>I$ zL_F})rx6dF{Y=Yco-az$#!V z@L*fS15f=6;(;C8As%=#FkuXiSHNW8`@j_7xDJR1t_GF@?*x_uyLUu9a15{pIP4h2 zkH!8EOa{IOOaYEdL_9FB6XJnmk3~FiYiGm*8+AcEusbke9JU`Y8TcYFMPXOO10&rK z58MMRSLN=A2R1nl@xWAI!gzJOKs>Nl55xn%2S$Lqdm~=ijfu+EN z2{)+tV!T65v4Kzko9o;#t%p;H`Kbbsg|y;G@9(XV=+#fZxupv!lT2=hfMX zlPzmQW}O`dRs++3Q|8y%CBW9%b#@tWHLwC$y0Ff!2L610o!xi}`sW38c6Z<$;27XO zUg;{MpMZ_0V!yts&Q1azxUSBg3`|~+^#;xZmI5~e z%Yi*_M7sgU18aaYZ$i6G!|~!~v>R~42DBS+H!uQhd<)`%e*=~SzXnzUTil9xV6WQ| zpMvu(U^4KTI}i`Nd=uhSJ7AtY;dHDQFd0|{Oab0=AKD#w zLD;s}1E-v2+gpI83vGK3@Tf($eF)eW*!~P${{aUAtsL8)0UQe~0#47h?RCH+;G@7V z@-X)c?2>QWKLDoz+fK)I2XG*8MG@kG<-j7~9^g9Q%}WpuJp3ZW1CP2K@xY^jZD*ie zfdhd9fHQz&fJMO7z;!_NX{*7CJO2zTZh1o7F)a>jRvLF$g5neLuWV4%?n;k>`u*6~ zp(%gb09>=gzM~!suV$jQ>hrIf^j_fRZ1NB_59-rmLZw3Dn;&=M=?XF%0zFbWsJF%HLO=ySb#{A@S>aOkfQq&&l)r(`x-vIqg=!rnL{Tj84Q;tMXTft8s*1-C!xRZm|FABXi^pOF}PsDZ~ z06nOEheNM!Ab%?K?;6No486_p;QSk)S3nQiK3k!`1wE+#RnWh0Ab%8k*(t&K6C2?i z9ePmy;n4pHJ*a(Bp_hyZ&R-1u8t6g!H$Z;`dQkqY(9=#0&R+$+0D4gVDD-Qg&-HG9 zPybGAY+3I>$EjEi{}g)A_!EUbYEsJi@e(0-w`bDn30eZri;QDWco)10bjrWY7Rn$wodi(`${wVY} z5syo%*!I=U=Zr6jO>xZzJ;|%jbHW{cIP`Ng)30*%tl;t$@5~PQq@5KF*k-qrhZhRQ}LEJAn56g+w|LkT`|1{{m z&x_iVeCs#XUB43Ov*u#{&{zL6-TIe7zXp0YuYR5*JMCWq{W|DR__p!G?l!Jg%b$j+F$$h&s{wPz2~y1{TarJ*!opBk@X8hKTsUCpZ3--{&BbdY0&?A zVbp%zr@!pzA#f$o`(G5bU-s$G>lFm2`p_nL-i9Y=9Nz|gz{SD)@jmKnearZ(TgS#n z;F=!sJ-m9lu7X;Acj$LQU+LB3^@*C&$3Smd616Y!=@+{BGoWWd4;lwnL(hi3#uvZJ zjo$=)7xcS)`bJmZ2K~&H!N=)+&=*4wI!-rkVObYLztoq%#LeFw`abAE?LP+k=u3jz zKZAObZy)OB?nA4gpSCKveKtX#4E-yduf_V{XYs0ns{OY?Kl-xZF=ik1H0YRuip8&R zR?iB7YJ4Q-WTC(9)zvunn)aw=B*D|?uTgu6*ApLc+h_vxRnXIYx*qI9;Ig1^gZ_?p z9VV=5)M&f28qRvGg=fj-QTtV2j&1HT?t^~p6;XSsPhafnJD?Xqzr&~B>gore{|vpC zw+@ZEyY1MbB?7OC+SmB>D_y-e^ny~{v+`}nlilq&8TvI>;~t1_`ODnp=RiLQ{d8ac z$!`9&&^xcg@!qHRclGpu09!hFWj@P z@#$Z?dJgnopr7m0XSw=X=mT$#+PC=h^{##&^oO7?_vwYMz5{ym4N?1DpZ=DsAAnu~ zJ=dpaxq6FMm|MIhYJcg|_qlp+=)0i@)qgVdR<{O^<2le*K%eW&ADd5D3;k*6LFXFx zL9ehdJ*)X zZMz@(^U#Bi@y*&`E(iKgzOn6FcWg_7e#_la`&VE6estG;0`z5@F~0lsN>|T<-uoV0 z3;FWrxcS#WzwjSXyTBK(C!?J8hyEt?yL|B*-T3X$XWkdJTlmh?4s*}b_Cp`}P}F|I zm;Y6_e$6nE_b=$Xeflm}PlA5u!+1``cb+-TJs$_07=V zg}&9NKjG@zp%*?AwL5wBvmDuJpZ(Ay(1Z5fW=G?>t7n7Hx>`>vh^eLwU<-`Jbyj=gK3pZqee z6MgYx-T2MWGoh#ZbbWrQ#)s|D?}FaZtIu@8_4-3EgZ`mckKg0wZ-xPS_A7Y4%cpPgTC;d!K>W77oavtGsXOTPHnTvHbG$DzODyAG~&uY=b>U-U-QzQVWvUgqw|C^k+WCHKMP5K6Be=KlHy=NA0J4`eUx%37`~>L5U*O!%SHIbA{j#7xi|5+f`PRR6Z2h4>hUeP4`t(k&z8U%xUkA7U zcIb~Bh}t>6^;_Vs-+t&e^g&)-U%ooyLo*B_ZNG`yQ+@TH=+-|8`Y1dl#D9iFobnoC~;z4Uv`h5Pbf|1&^Nw)>5gw%(5rq6KL1<;{S)X- zF;^9vvwUieVQGM@d0|! zwQCaeZim&{7kK02m%7WJ0R1B9LDwc((2r<_=X;uZ*Kbu*S-&;V3tQIOgM8aRIkx?w zuSuv4ymsFX{dVZXz4g;m4|@AUe-`?CUOoP8xBksKq{pmJ_-62&;yVE z(4T=Gbbguz{Wa)6dh^G}&hOShzpZs`y>nE~_E-6#kM_o6j!I2qDSbQiYUmrhdi)K} z`r*)L?T4P)rq+Lc3SPxF4q4V=ZEFMXqjiUV9`sf4x_vV~cAsht^yi^J{9sefh_@`S(Fz+@sb$(x*3b z^~PPXeR`tr`}Dr9uAcNLNUF7O_pRS8?)r^^{sZ)&!(kBo#kgh?+!ia+<7(hv4imptgrpgblZOu z^p!*EU%wC*xefX%=wEu{$FFU)s&S(aRT5Rl{qRgUsW$NV)vP=2BSAmK*9Jq~Hb{d0 z@X5j3U;^}4pqF4-ZX0;+HD^KZIUM=Db!-$nzgz=-%qh^l@!soy=;_cm`rkoLg(( z>TACn-S*3Z{=#fLx9rOwdzNPn^k>h*^Q}I;k-PlO(C?aq=YhTL6aSpM4{nD(DGkqi z`{EyQ{);; z=;uJkt{7`S&px^adMfnIUOoN}w|<+U?}Hw+{kAK8L9Kn0Z~d=}tv~ehvhh5&Z~Hv$ zY@ZO8*$fwcb&G24o4w0d_kgZ;s^IL$z2RAcXX%5kK_^3B3w^0KM||x5Ob+xlgTb z7PxV881#$1y61Vl-q7bm4|#RZ^AeM(Kjzir%iZ01Z z`rQY;W^t|kmN!0rr<;EV^xF$+?QfdGAm;&%_Pghp2cYM#sI_17#lPgnx9EjA!t;am z-q2r!9#p@{(4V*<_#8C{`Yz}}b69Jke+>OnU;SeDVef-JrZ~9$c0eBsJ!tzKfW8TO z$eZ7@{aPHa_5Hr~|I}^&-q3R{s>FpsIpd64MmhA~{#I*`_wC1{oc$QGvy8XlId*OEe)BJOZ}r)a8ApXllXp$~-~G#57mdLHzk{!;{fC-k8D zt)qUxw-0>b9%~KS+f&Cx) z7U=cu|7x4G?Tfh*=%*d-$*;z-5zcgsr94C7`C@CW{U-i)>!8jT^x?$mr>XE<^GxtL zcQN#jp?~Pj;hB@)0DZ%=m~-%rbyM7P%B|4fsK9ltZ$Hj<_v0$)i7y0Sw?v`806nOm zCSo$J%ZtJNbU5{(bIMfcRfs>+w|-~1{iqmvn{B~!9vh(dgC5k6wnFa@J!tt=(7%Cx zvafvxyX_N&-t6TD*1sRV`vyJat-og+8BYCluO6>YsMI)>3Vl7|gX&)l{YL1ceEY#E z?tZWV`qQrl*KaHIj;{sRuZsFsU;SeD45QE=NBl6azQox)PXA5pk7F@(>@KlB?s*<& zIP{O82aO}C&_}=C!1_ZU1HHBHeCw-CteJF5A-bW@;%RcZGay75Z~qY>DRj3Z!7fm(6969v3mkl z(2x8mc>Ik*9|QepZ@j*xsMmimzB3B_39laiu)F->(04(9&8Nq%r&6I`@NupEsZamN zjW32iqB{6Ia0B$8pjY_fpLXN7Lce}*t^Jr!k6mw8K|kS>;O!TMz8w06zIZ+9q{i<= zT$=TA}SPk&WwALX0VJKUYqOFRkhUcz_AgT~+C z(3|5sMT&(P(|Ss`^+7JpG&21&%Rg&@YDG(>KO-b;r00=$9Q< z7x*2qYUl&-J@uetjD>^$KXU!99bozo;sdA7l$zEdCMQSH1Ro^M*!)q72>ax^;`-}7ya zcX}R%f?QLm<67to z6YBz>0lW|TR_HB!ec>>tFN9E~9nd#-sLNABj(zlMy z-Tqz<{noztJt^Ng^IzO^=1S=6@qPUQUw(a14}q(J{uT7U9*Kf?JD-1~QwQZ~i<=Nr z!gY4O*%#*OzMwoq;aNW(eZjXaPt`j%^hEqig=fpe;I=4+{yOxaw%7o@271u=vlaTZ zN%fCEYWY>vgW56*{S^ErSI{%1i6b!IIl0c>h+J-4#cy!8)o|!z@GgX)zLX06Jm^7v zsTg_=^q{`90s1cJbA8L#Hx1PKZH3-uYVg=r1-&iwPTsMjQG0iuAPT(|-q8@Wzb2lF z`a=&Idxt~M#5){z`s%mcZJ$)=t?_P$ps}?WdUxnS%ijQfG4!CZbu0AK@vexVv9$_% zHT03*`V~9X)5m}4arphSpyO|17{?~)LHp8h=&Pac^tP`WZz`N7wnA8L8ayYSTW7y@ zEehWK?WNdx>}q&EI0#QWkEhYjcB-aoJ8y!1WmDT8-P)^52`U5Na?qFb^NWC9fEc>w4m`U41E*ypnW(E`m4}`_Q4Y9Pjs{ckI!Y$ zw?Ge?YpZ~MM4}zI4^~4z4tmf&sD5B*0Q8`JFa&))^gF%VxKWwA4~3z3>S71p6H9|W z5PE%cZK{1spbv-sqPOkipLO@4GUy*e&u<2U^q)oxo5{AXfZpeL+iv2UE4JLZ;%ew^ z2HUn8%H8-;PI_nisvmg$b&wr+Pb>ueYv?My8$Ui4ABO&J1Mz9lw?hwm<1dKCmq6bS zy~wM_^T8v<{rB_V7Wi)q{I>=E+XDY>f&aF^e_P=H=N3r7`HK3e_ZoUXk!Krui`Jhl zJLhF8@;MzJ&nyWn$FUq@`52bNEVF1uSXP(y>QfR>)@tnk_4I8ZmJ#nH{(~5csQ<6B z3SIwBlq*>7D)#5Kt}bEJC(LqNU9{3zmi8!NS)F#PPbtgJTrSFGEH`yjT<5VY>tDmN zv|qvtdik=v5X+*6S(f@)FB*Q9#V`3}dxZ@>#VDt-+=}%rVOcK)>tp!;-TdmBLVYTj zPwH1=l&x)g$^Et63bDL|KJ|!#`XsO{+b5Z2=|5qXn`pb0V)#>x{*Y#rBSyKzD3==L zGNW8>lq-yKrBSYC`8d|MtWvM9v`^YDU3U8u%d}f%EK7Toyrun8j{H-X#c#b+e_7fu z#nc%n`(FZe@rPKJtjR3P{vBpn_UF)U?Ua0JEX)2JVOh4Hwa4#IU|IAK%d)&=mepmn z`h;0-u8UThp_i~M^(iy-a+YOzl`J!zRn4-je+|o{#PFV0M#}P89?fzE%d))^-uL@M zEStKNBZj|(Wm$f;5uf~l&L8IT!z?dnxr}AWpR8W4hmVxQMmdG$u;W5|{}1z}8Sy1X zep!A{{VM5~@uJ#@uVGnDxv5XZ$9jEaf2wA=jh1nM(Yh2vEQ=myS^7g7%W8g2eacvt zER`(F{!(qk*RU+(Y1yYbvV%@%RkO^Zm9WoW{}9V~r z$rSUM)Ku$|Kdg&ZnED@;rN33sFWV#GbDdw75@K2WVU|a7d1Wk*X4(2eM@s)oVOjiX z0lG7fg&gIEznW#KU-EvbnO>l*zvyA=Qchvn-Tp>7!m@1d5|(8trAE2TD3`M=<5306 z7qC65S(f&b_6}2*?Ir6U`BE=g>Mz?PM1L7|GoQ3~C3PuR8|50KY<=b5UI|7yWR#Ox zmhn5xvfEzlPtu-cM*m7N{An!9`bLa$3CptmN?ATu+Jj|T-vlGR($K2|%H5gI`dY7v zEH9a5sb837*&b;u%l==+vaEk4%jWiy_Nu1tF5h{$7SGSIK4F&S{IP`Pu;^@GIiIVb zZZ1#yXAN~3pRI4CCOV<)PqKYWsf)jiWohr^gF4<^o>@Orm-Y=aUh+$QL{FhE%S&T9 zUe=%OC+7Sny0kCsw*+lyssZ|h%rea!mH_6<`v*XMuQzGi+ipVa@4+9$&L{84+f zWP6p+@9sY=%k^P3%Vz!L_!Fwp?IY`(#X64fQ|DC&xqUJKa(eACynpCun>S zJ;Z$G^3AfjezN@Jfci-OFm-9~G?xFUzq#9!{qK+bQa|Z$7C$n6%X~?hQD5m#5-;0N z>M#AvEK7bt>A$4`$9w62k)Qqjy@X{sK9;g9%PTYdH7v{cYaP=0r2i(fEaO`W%hDes zEX(nslx107Im?o-!th(a=;cX!g;{p5M_6{}uYS^g*`8sRhqB?)SRTf51-5oZzS6PFS% zBVJFui}*P4W#Vq)=ft0gO-FJ0L|uyg-)v>$Si$pG$wXZUlWWEvnOv%S=EKxIA-+G` zjMc?3*=CkrP6Fd+jrZAqSE!hzsW25;ewgv!QU6__5R>&EVU{I6h4KBVi`~TV6wQPw zNPHUO3#glMQZAXQ{|b|k_z2@4rY?RHE2e2COhMvHWc{g|aZoT3+jIZGFIPLcOmp^{==wYFxF`09- z)AK_8`uFMIC;7yF15e1voZGMel778Nc@|{+8HaLX>s#BPEj3KVL3vbH-wM&7=E>^w ztLuY!>nJO26pe%s`BH;d&!H*5dH+rACUJP8Y5OYO|5g3eXD0r|Sw~xPd~rzpSB!ru zPr_(nG_C=};V2=;D^c&pANAG$1;^jYlKoYVmwU;}e%MwLp<;3Pv1Qr6#NU~`>^H)X zATRra@UzKFKNr53y!2n;Zw9Z{w*%MrV%BF9`Ss*OS`>sP!6H3h9=DR zJXUpJe55^V|6sW(v$Wq_?wjO8TDH`CAXU5NpQ(u<(q@i_U_oi!~$X`c(KKakc|3-cx z`KCDWRrSf7s|n}#LhxrV@++9Y`JE8(Q^`NDK>OABr9Laj&&<;0W8*Y#CSREC=ieZI zH7hcb{#x>HEzQ_`CSiS$HVk;txFx+a(>SPe~u>q^_7};e#ZlUE&yL&f4&&J8lUC)jRYNL z-RAJ^tZH7T2>%@U8iW6syp@d&ijVkh@(Bhn`$gjQdV%JCaWnalmaWccZ`ID@Z_q>! z^3wk!?BHFo+@@vSS3%zq8+8Knxbq8nIqv+5JZd}kyh;1neJ$w^_mMaI!wENQe@KIS{4Ly|3EA$4 z9j^25=KvuGY=<{*~m@41Oc|h`~QkzQo}7 zk}oy*xR&ha2H%BzxxtSlUt#d+r#^H|cJ&pUiOtK4Xxz4V6+f80^J z{VNQA7xI+`Khoi`U6@C(#~Kv0oehk@S}t`P1nS8U9PiCmZ}7tJ@Ma5f2rYDzkQ902YN4H`7zobtL z#XrR1+gUY+|1A2gm;L?~^jkUp{&^GqVfv;1Pt%`Z_^aqIqd&y;t))L?_>aYefYX2I zmvQ@4hi_*k8~!xFoAj~M zM&3M6>-@0J(}&wp&eNulm-Dm(sE;~-dyD)W`ek0F-y@nxBp-LQ_D><7MPAPL9t`UX ztYzfe)BhO#^T-b%zx7c~l#!oEKJ_t8d`o^2`Q}?R!OLK475NPEx6r?VeB6_oSWaH% zg;JiA&k~O-TM-l4q;t*U*1}{NKp)ves(aPPfn3IC;Q&tUTJ zkuM@I^G+GhYeMQdh5lXS-=}{e`Fmf`#C-CFIF0Fa1^fx!RV@?+yJ;^D-Ybh5jzJnxCe@noIt2+wWgNzSm*8KYvC4TJnwBYF_f+ zLB8x5KQHrp_oxX{e59W|MgNr{%}e{sb@Be=G%xM{7X6d^X?_gr^C9_vk-vcax8xW0 z*M8}5jXLP|Z+@!gN7LVpe793HKal(o@@JAigS=dqy{;yB@saDYarF0K$6rN%D*1cJ zKSRER{QOZmkMoXsXdB4CGg|ZM^p}$#JVx_(Yp^QGe>heXx6yx){4X;!@4Q1D+F>1a zI}AHh^S9IAi~JjZ*Su`sF!=heH>ZPF$3c0%J(+nHJ3QtAa{b3;87H4m11vrl(=X%g z;5#)RA}{;J5zb2m$$CqZ>G%=UATr1~J*M>Vh&KnKC9H;JnNUzs0=9hWwz2wc~)KL#>zZ|Ee9oCWW!aUNRkC8XqbKoQX z_Dn@R>)VdGTrc^afvneBu2+e%UUIy?{zaXC2@mlKnBYCr)Mce%Kl=W@# zlIFWGzlydu)oWr-X%8Y(eMZbKHNBqB#m+$ike>6@oRDSthkMIM?%lCVPPjPr$ zR~pOBr9Xw=2NM4k^j8@Ed&pOl53?Pl{Wmw#?Pj)r&&HY$(f=~?sPm}t^A-Mw2MFJyfu(VuMi7tk-?f0Ec9tmc|G$GuYW=KlUHdD-8k-9{g-^GLf%J1k0`klL^+sFmAHjBz<+k`s{mVU(yt&-_ z$jfq{4p{E1;GO;PL%rQ~uduAoh35lq(jOXi*8L~N*uEi$Z)GJI{!{2rGyLb!A2R&Q z=#Lowb@V42{)gx(O+rg?|=@k_V;STKg8iNpKthQ(qCiv3+b;h{8!U& z<@?8z2kE!o)BP&N(x095Cm8;(=}$BK&AaOQhYWvDhsQjp;U7zXvf-aczkFX&`twTq z!-oGh`sMqKQvYY@Pci%-kWVxCUzkU}_bAJ4*G;ci#PAPtc+5ZZdyul+>GYQv{>Ajm z_a?>vclt{W|NZpK_bR16uhU;<_`jrIz9%X959_Y$UvBu1b9h`g@q3jruQHnc3d5gH zzkF{}>T@yum4^Qo`sI6_;(wa{YQz5?{VDujrTBlQzsB(YKM^W=AtU!5<#LEd?%HDctrc?F+?#PH&L)M>f$e?(sL$a7z(b<*`gBRZe&ng6t7 zHGjXREt#kKHKh3`$g3YRSNSI$ulW^{vHmG`-VpTG4xj({<-w08Tm`-uQB|$(qCfa-%7rW zJc`ceedci}{=4NmPe;9bTf@k=DE8NX7Wn$EYvzMj$l?_Y8!Y ze>451hW};y6iC8i2oe= zQw;xd`sIBOQvd7ePc!`Lw~|zShQEe|`Dpuf!U z&!<1x@UN!7-0&|hKrKcYXy@T=b-QvI`<{xuwbI`&sQp1UyoL&--Beir!> zgD-M;JkQMcaY(v@2@ye|Zs$@v_P1FounxsfM{e1*Y} zb9h`gv7VAYgZ@gxe+m8a-VyPa(O+%&pQT^kOCo;t8$qg`HHQCJ`Ypc4L;US=V^{gD zB7eUf?C_YEGyF5?PcZxi^oI<8Dg7bC{{a2ThJOeB$%g+c`oo65*&tp2u;K4PKE>e2 zkWVxCxenjTN-^@6&>u1Ux6&`~QIPh0hW--6uYS8n?H3Uv|8Mk{8vc%hwZFvhpXBhk z&NBRG(O+u#m(gEt_^+qGjD9&EdyM`H!~Zt8{`ix0`$r7Ei^HS+xm?K; zroY7S&!OMq`)`uj&KJ>NYWQ!VU*5kX>$R2sGQkNc_f$y!*Xa)%{;$ZV7<}{Ly8dYf zpF}=l@Z%jG`w`z4B+H#oe~ID0lzgedZ(^PlBhQQUml^&~=$H4J$a3RO(e*Dk{2}rc z27jueXQSD!97(+oc0RJ~my2H%%_ ziNQ}LUuy8VdOVr+JVbwq;cqueZ-t+O{M)^c!{a#4dP={V zLVtqc&mkW&_$$aK8~i`WhYkK!@+k(tpM09ZA2vq!D|vsH^s62Yk9j%6KbC%ZznAo@ z4Ejq9|0??B{a@n0o&Hk8|2+9JgRdrEZt&{I;ncXD_H84lCrJKdG12OrhZz1-$X6PC zD*0-Izrf+qpP66!^G)>E82%^em-mm!cHcw4b$(-CJ%1*jVDLwe)AbJ-{2=nl20w#* z*x(Bt9@n|7ztra%`cn-5L*&y8zLI(5J!jI+|Dr!)_*;(G^)E5_6Umnv{A7o3Wm$ZW zh}3ft{bh##a`NQ{e-HTzgWpcR(%`=!Uv2PBC+L0^Vtr(Nk8^ljzZ?EBc zh{4}TzQo|SkS{g(cgdF-{7>Y|4gN2abp0y~ejxcugP-p3xDUehlH)}I{ndv5YVtJ( zUrydC_HW-e$tM{6LGmGkKk_tP|1f!}kK8}na&5dbK43b?`N;jFugPP$a6WRsFL|Bz zUrb)^vp+~aL|*PUy-8l~H=Tg`sQYUlUZ?Z$v2|-S`BgV)ek1e9{j%=sHIK_E=Og#a z&LfX$ZRaE3V_Hl8Qu1=Yuhoq@&s_3ypRtg97xHqy@}XO_KbO4RuRQiv&CC5tc^+U) zncpw>B_AS>gM{;u`;rfB)cyyVU+&9q=yColKoz9U~ieh%8LzWZ@`&UVNBFTNKq zME~E(%lq_XoGB+S@72TZ?0j~Sm-p)l|DELLd-a5GJ6Vro^1edJKiJ_h-)r>Gv*?%i z8H)dW`Xh#a1O4*8L-9XLe~IC*reEHNDE{~6iB`N}l`ZFE{*e(4TxrpI6B7sD}Ou!{2(UZs!!kKhWXvoP*(?MSsNbFQ>oS z@ZUs#so~#Be~sb)i2ic=rG2bvx}7b)KTr6s4v%&=@{gfE!SK(gU*6jo;&xd>UfwS$ z?e-7m2^o2I&@badu79$Tr@zDFeES!@y(G^J`oo5QDftwGzkz(3 z!EYfSG5Gh$ml*u7$h+j_I3nNIh>Pevm@alc^1Ys+uzsL%gC^S2wI4=ih%>WCWhgS+eFO9c}(JwtDo&rZ}lA1kpslE1Ep<{zWKANfZx z4yn&h4b~*`@BgR?)h_C@heEAm)n&SFhunBwpObZ%brShe2eq+;{#oR|UZ9Qq z8jm%X{1QH=m&!a#$Pay0Cs<7W@8n-)p03P)5BY@8H6i`*Meih{x``Nmude}@?Vg@Ct35-uWHB-$kn{GTii_DZmnL_39e_JPUQEWs{OB!A4&d6 ze!pfE+h-d29vRyIGW~_*AHPQPY!2%p^53!k>*y~fzco?&SFs&7l25);^E^GZ9wz@l zW6fVc|4#C`^EEI1b|3j;f6)s(OUu?z!0{Mgj?U&=x2=WJS z(7sFPKZpG8YE3*%K8O5WynqOiKcD;>z7Tpd`K!qP;|;xBR?E7Ze5Z+;U&a0ICGp>; zc@D?c`{aA`LXBT*x1!{apQim+Yp}YUrQ2cM23?V_*ghwdKjwAKGo5t~`S$a*aY2IC zmyy5oeO<=sjq|yl{N|2&frrSKk)NKfc{zW(pM3L4I{y^b=PmLTf6;zEW@_yvf7wRO zhcs9}kgvQ}FHpA2acArGeL6)OS2F*p>+;@ z`3_HLqK5oN^50|LLVe`8{1o{sIAE~3t@p`K=6J=;V;v$t<#C;7hXyO*9KBvsBf9># zlkZA?kxTzX@|WDFiDL3;9hvk^4)+xw?JM`A{c# zn|Y2SUv#x@hcRrQVdO_}eUG949P$TwLU97e&r8WKR}%~PoW|q(HRKQQI1+YL%X)zP z&JKEk%XFCa68YoCXruI#kH|;zv{A;ZDEUb}b)FLDZS$&j#`rbk+Q)%u`PO)d#ilHS$}@w|`UD1&rARHNUfhjKFa<% zh|8Tx{(0_jE!eLj#3nyEO3z`NZ>dJ6sc|8{h--L&!H8uIr=T^P)bJ$xj-t>+_zbt+U8qbekp? zl3zl8e3H)JmHb-rFK{5_*X*qu$TzuM`|}Ue0r!)?wp=e)j#E3x|H>WuAos72$WMDv z#}CoztY66Q<_X}BtWWznx*cBO_k2Ili?L27UxlN;`kYDsndIN*g~c%P=aX+bODAyq zIr-OGX}%rvZy`VSHl1Jg-(BPn@VaIh^Z!IXv_mJjg?U=1>H3d3M&}t%K8gH-(={*W zZzIU(-Ki5V)nJ`N{=CmL@i6&(@}>QBVpPNV6q6r`kP67rQBweg*JjqAzV4`{!P+xLN4cdE1$X{QKk|`brb&cnz9ZI+?eH+4}X zN4S&YUT5<2dA-n;^*M=ri_5fM+J8Lx8+e_?=Co#jkF(lam7FJJ8f!NFH>n#R_-v)H zg8VaVurJA%l271!T5x&gd>$m9#Qt^>`3mx1@kCz6!*|I))I~2>`qh`@-_F+UmdiYc zW$O0nQ>%Fn6IL7Y>$YirIsGS+FW9DuJo4knFaD<{X6WX#=8(T;g(jM`(0YXY-qt#Q zR}I!$^2NNal>D2?cU!FUsP|T@&sOr;JZ|2vX{(a_5j@^qLjDl>*LYrcA^9Wb>vnh< zTT^{1S)Xp?k2_lvS={i$$e*xJ^Kv~qoBY@^%@1Mz0`jBo)kK*2uOQ!*vkxcs{NN!=s^CZ-I|z3zAyPBpV0n28m!^u3ny!% zwC8N{o!M`5m}fcpAHL8;A?tZ5`J+D6i65bV6ZuCb`13zQ{ssD{(*GX$zuu(@UOrie z$lvh2=C5G=k6ftR;q1>fe+=^+L%xL9#f5C{e&ly@zI|^K9dHKu+t}awFi!#bS=VXe zW%AdM|Eg41gz2n1$*<%6_fCyQ! zU%FmySNc1WpYoh0+Ht##0Dp9RTkBAN%{QYz6?|*{?S=dReq{iEBluSF$5_?;y{w^3 zwk5#-j^g8wvgCJ`*sRu<0sf}wcq)I|CAtA5e-H2>Z^ildDCNiR3YGD9g=Bqaf>-Te z4bVp6FT;st$U?6%e(!4u{PnfZngIT0wcPkb%UZ4L$<1YL3h+O!{HSNSQP1rG{tpz7 zc9Y+S8p-wj4txUYzn*!dNq$lJ<6Bwbr*y|>H?=zAghAD_+F0%&@b#_l1jS>y-%Zn* zWx1J79&C4GyR3Hn36}hxpw#E`fc&?BSKEcdgC*PLDJOrTwa2LE9`cn2|8+qAXaIi{ zPDoTc95V9s2e0ZQ_2BfaHQVu{AJQ-V@Iv}STv3Tz2VS+C{N5OMKkGilOQU%3IVV4! zALIL(CC^UsCD-chSl&Yi{VO0(9Qw0buKZ4!EVrZLJ3Hvs7nGdmCi2!~ zy#V2#AuqqfCw#TyWutiTd-~;f_r!lRCN5gV<2efc-bWH+`aAqFR>;_|PY18+DZei$ z?U^NhV|%SpeosB*&zl`T`uR1w9Wp3vcX-r??+Gs>|9(K8Z@{bemwL-~h$~S2EzxSv z{w&MA-045~oug5jk>h4l_*>(A@@eC~TTAdNf0zkm`<}r3=6E~A$sUlx+*$&=WuCaZ;P=0Ok z{r_tK-=a|E!E@c)^>*CCsIKI*jBzX(ysD4<{$YRmCy|%mO_Y8$hrIkArtnLdzvODY z-0jS>LHT_(*M<87_zEY_?~j{1z=!I|^SR1{`pfTsO8uK-VnOk3#_Ay9dn>-4JR`w7 z?Z)=8;N{zPlPF}`gK$p0{S>D%7L?__@YeNxH4&*58GZbxU(SM*m* z(}b+okxTUY%I|bap6=k)`AmgT&moG(@g>1HzF5=e6%_UBlW)zLlRh_lPEq;-3q)bj zlKJ!d%(F5w3o;jE7Zzm}%qhy9GcPAUFS8KNjQlwZa`NY<=gi3{$}cFKlfGoRH7`Fm zl9O4Knb9Y?e#e~o*?HM>(hCaGSIo)GD=JuF%`ZsL&7705BsX^jLOi88a2ENj;Yi^M zR3kTBn7MpjW~3-PKd-P)UUn|(IAhA3IWx|mI3SXK+UaK}pEPgs!g(jn%N@ORNx^`T za|Sdl_itDp*swgPVR>-F@{oq*p%zweoN6H}k{{83$($ua z`wdX$F`4tu$ecg1aB_NK(dZTPa}SB1>~Kwe+~MBxMT(!%J|HkPrxIjx-~| z=C)jAyKGA=j{A9h&*{hM$9XghtJ*HN>$F|vt}6HANW3<{hBbr40wg4m*vta4VlfMT zz!j@muoxkP?h3JD0TRO(nUSuColy&}?!D!%%*aP%WaJl-nNi_wn5C;MoST6F!C*4k zoB7E*hp6}BjQzLU7Br$c4*)olj zg`Z95v7a&RVl~e~%!t$JS^Vf$8gJ@BAA9`<$l_!_d=NAC9G&k?eCsqXXGsveUHJ)S zW-5|7&F-4cMNX$_Z3mkiM z>vp|$W1Obg?tafSWMK8(~W9x=sA~K{Q#$ARCs^nLGiZ~4{7Me}x~mnVC=Uft~Jdi}De z>jli7?y>r<@V7XWuGZ0qr1_9db#k<7_n;+NHKxzeIE)MZeL6Yx77yod+h-GJc6oB# zA0NE>@c5Q{YfIIAwWaEw+ER5tZK-;lwx#N2+Lqc=i?}VdFQz(8IoXkuwQ$&~1|%S0 zWKG7(x?Bd)bvPyr=)zi=&6Nz!t{sZRhcB`E-|^PA-og<`?I2|K@Yn_aAxS ztjW41)?__&)?__))?__+)?__;)?{D+;*vdGkAXeCrgg2F=C$hDr{&9Dzm_m-IyAD_ zzjjUk+FHo$<=2gE&ER73mfKguzfKMRIyL+QHW2IgYWN4>Dbv^R57Dn=`WpUtHT>~v z_~X^^2e?OUU$=%o-5UOMYxvWxk*{tIe}Ff|_5)PQ={4=^)r=n?p_$H`V5CtG!#tb;#JyN(Z?I*IDk@u5@4hfW)9_|q=&IN2b{k{r%oSXyL80+p7tCLzH*h%T z6}V{b0tOOHqhUlI#bO$jG!Oh_w@d;-TP-K>jh-dJ!$pbFUa#M2B>v-jIB)`VYp}gj zP9u!Av7*^)eAe#9yczJJx{Wl*Dvbc3-ZBuo;jJ3T0uQ@Qn4f9pCtKXf&Ppae<+&CiE!$qr-@t&f4AFSgz6;4JF*u2gXg`D0pCb{5r}h2_Y#|(F9c6%-Qa3 zf8E8ZkIKR6B?Jy;>+ve{$?c19zT|nv8#t>Pw?VeioG?AT`LrLV%enu&Hw(shjdT`2 zDi`@N#0HW^zC>mtHdAbh7 zm8I+g@-!ybnh|jq6M9~iO612T%H(1dWkDl*){(v11)?}^Nt;t%gTa&d?w)D zm)MqD1je(;?F*uZhuqy6+z0oC!uk$W&1OeX|0FEANIyj9XY)b$`1=5D{@I9$B~gosB~kOk zlBoG%N!0wXBx-(G5;Z?8iCTs%i7Z35R4qfcR1K4DsUnxyc(u`rd}6cJMk{iPRi=$r z!3c~F6L~igdgRNeX8@$6{qZPTq zy9+j2aUSu`f`yj%6>O=>2*j3u94)*4Uw~Ul5iJGI9L_OIoiCXhm5;emtiR^>6rD_4O zrHXB0$FYT$mr7fz7F%1Y$SC&1T4-4fTTorHcJ!SEG$qt8iPY za0pNPk1UpSb}hJH z`SX(7&FMmu1ozsjk>++@wTc$D0!mc`(Q0ApZ+0+`f<>6&G+1FGwGWbb;61tN+3w!i zYCk@?51O|IoLdsL)LIfXyDW)XvMh;O+%1V(rCAcSSXmPF{In!8IJc#0sj{VNsj{Vt zTw-u;qZRqY;M_(la*DyZjaK9pgL4ZlYaCmuC>9K`ZM33bFu=CailV^)+d|6#+mtZKDd}> zMk~sdC-REHxvgZ8R}9W=v?8wl*|W^Y^frzdg2`9qe-@sMY-yUyy}U(;v-46dPQFKMP32c3Vg=imy{~< z3b$P(T9H@yP@+UD@`{g7S$NeKd1a1HS%_)>QD~PrS7oIZ`9&9V#KJ|6IZcsev^gvm zE>1>sn#4E43^wd=$${EzdC-n43dbw!&p-PJ5q{oXgfEbho)+>z0^gKx-7nu!nl+n6eYYDY%Zbn95GTVBUx zVC~4rnof60b@L?yTfb#+?cijjShy@Ld_lolx6CyeVbd*Lp=qv+uwA~nU}>7m_ZFpv2KlP zS%$H0jdNM{AG>@Tz}7Jth`KTmrL)kIw8%izm64_^yMkRAX}U7fbY-OJ%2?5reY&oU z6>N3JVFNmquFuI$UD^W75HWUT1QSkaX|wyum7T^TDSkaZS zqAO!XI$bL@O~#6@?7MYk=dCLvO;<*mt_&qzgn^MdCPPU&dmvJaP|}s5q$@*7SB8?V z?5TBSCf>cH=$Jlo&l)=DK%2CTPMxRm{wH#v%DwR>oF?Og*Wz=$v-Ks(v3}~eswNl8i zRw|=b3K`r=W#}bJ%2CTxM!7;640NR&TJcIbYGsT8u~bH_j4>>h%BbZ>?a_C*KPxp;R>h=sB3GCv?9|h?{ zUVcYF8#!MG&yR6}MbBi#;=k>xQM(6M34%bX`>i--JHAtq1M^t-ebSK{(%n5i4Ng}bI+A51K9!W0P@jN_KNB94S; zD(eD37e|EaaOfVeFdl6O%e>~Uxu<*ZL^qTXV=l!dZ7@Y0`ONQjg1L~eC11D=?gx{} zRYaVw%GossI0KUqLo!{Fv@PkTGA`t<2ba(@-QT6!a5)*byE7F*j0nC$EWC?gN!_9w zwQI&=$S?=SK1wl+X_0wx5-yGbzlKp1B#kI|#BpvtT1F~5Fj2A?+0Ybyu~{GTMwcMw ziVLT!>~vZ>3F|ch7st4LzK>|5vo|4*vtp>nv*Ok9b_ZOH;uIos87!9jVS+36I$Y`n zgu|b|@}tQdu~XL#Zg*hV&oGVk9HblhbR(EYjjrRX=BQip>Pj-tf&4`qaC1Hn#@Svx zj|(NYTsSlJt!+$*0tkiiSGvsIZqXUwbY(^wZSW`WP#~3N7}JKPU;$TmiSr)78Qi1b z0M84$ADUADADb~YqtofY;4TWM5~&!ckR4!c+});OBQ>)d$B55qbPTMv(kw8{K@C9K zDb6{Mv;BCbulE;2hvpIYz+{XG=?6UmWqBaO4=i~V_)F;6;sxq9#0e~*+xZ*wth?@? znPZr)6&;()KKymKHlDMs<>I`g7PW^ULBP*PGxlq4c~J3Tx=*LJdG6`E!=n|3Fu_@E zOp?>IdXL}hQ5=BP4fvAR)^}hjskzH>`8FmqPLH5H>L$1<@i2)oi!fE6+`JgCMIk}y zjyzigR2&^SKwUKta6=Q^rl6#((qI}l zNjIDXkGQB$Itjmcx}*sF_hvZG==0P#9yFX* zkkBU75!`d22{T|AFEt@^wshNASpP^I(dA%*mSe^pVglt;u7w3uEos})X&*`{bPEA)+D$C;TGMaX z2_0-{RinNX2Hb<&8IiCTKyPG(0V(+l9;~(1g53aZ8SoG&(4|6MV8sAx3zl(`T}3m6 z04F>xj0S4Eu(?P`h*=1iGr>v+rWIP>b(AjBV6r-0I+(k~X}TVQ4%}H-78Hj{v82mJ zO;_WLtx%(^0C?g7V$gu-$wsez#Xg)jWJfNOTyxK zHkPwz+)6_L?58C@EU;mT74RG_qMHmItO}u|+O%0<^uJ+rtB)mZK3BRlA7HNyS<(yO z*$S(G$?bXA(qBW5Tsy+({M#AU|O`fh4w>kuMM^KENPGBx$1GH_Bz zqEcI{i{*_Z7A!?zFT{~4XqR5T0GV+`5NkvMizmS_g=tr5B92gOI2AB(+x!pkG|&va zSR#tLKi67qJ!JuJ)CdmLhYNHi#U+=J6&rlCXckmNtAjRB!2Tdg5p`@25SG3Mkc&sR zi4lZ4DClSpxP(QrkPpC16$Cx0B?SvFjoXSGCk4-pVNAz^WS?lW`#7Gg09x3Z(?;1up;pN5U;PUDMFoNP8U90~S4u{S~vo4`j zls;8ov?%I98e7s#@B-nravrb(!*&~~1~u+_UgzD1-hiOT=_mLd2*Q(nezFZ6A{si) z%@vSMw4u|z-ippaHB(N4h1DW*g zUW^46%3@TsGEBhf0R`aIGGU+}JywCg|s2;Tz7=#kw5Ytj3v zItKD?sTqj!G@U^OEqKMMxm$#IOA2%Y?*Q_fVawOy&9>mMMNv+;&n;|+kjS7hn+mdp zU2$VL81`bmDRU<0kK=*mRkesy2wQ3c`kdmE1u%HkOwXHJobA}bgl|Rrd8 zz?SszhO&bT5MoI^V~||1%vyRXokMJSVJ^1V%=12-^;qL_{Wf=TV3FEJ1mysn9j9YJ zpIfC*AS7DHs2WI<_o{&}^= zVPkak=5%-PW^{UZcyVw!x*Y7jIT#gOvH7TaeM~Nf#`)WB8{2p=7S4lZ;AgZf7rVdV zVL{MDBfJ}8OTvzi<)pU}ih+;f{qXQ&cyuy&bFjam^nhZE^_a0~BSL25{pG>=F(@~Q z^tunp-iFRvIAv3H*0s8jy^=|r-~L}OR&?aCv>M?{8gbDd3q+!NrhVfZDiKzv`lN({ z6Gfj3uK?{HCbU`(55r)tQ8O_bNc?sloFG<5VJ(B>`w6RR=4Y$)O?Vev%mUIXY9N%8 z5%bW@V8b=28O#+d#vD3v-7`RTzYNqccoZkG;vLJy!E0e(VU^*dH)-Za6dF_2tU}CJQ%VNto8VxE@XpIiK$Dc8@RnH{-!z-)VOP|J?w!4RNRgYb$%VFOT==Q(%`Agc3nSC-EvP6-RZtXI-QjP)yY4r)%NgwFx zHqa@xC-5pJptzPGQg2h`>?*Y=uZ#?@qE5%+!|c5~9)A^1&fec09qrEs!~5jbO*ka( z(wEVw2lF5wM(UK}<2i}UMk63y>I-b!Q&j+*5n|;j{{ehj_%2Zc2JN!cX5FXKt2oMS zR%27-orjI3>^l1Vls{3{9h~t9yUH6K1@PJF0c3E$-=z&X8PVtN{>d7OE@DLN*=Oe%mfK5P_Vawo2`V8ij&*2i%KRz~wG|-^zhjpJGmq zOC^*lqizv`>;SV*w@yn4CHVwM%MBw1LxF$`zN|K#VHzU_?m7T&p>ti_n?xtZi}E<& zgL;riJ(o!gs_D$e*Y_We&-V7($^IMflNa7x&0bAiaFOgde_ra8noJzdR{2}YRwH>} zjD|3r@6#c}(dc#og|4*nkJlgsCIPS|rJ z?Bc3fJEHK*ZcmAyT@q9H;j;D9?tCsT#ig}Z>?Jj@=Q(ZXIn?ZE7k54}JdKcbcIb03jUx92o z#i#jZYmU+E#|Y)0xWa}F1gU7KmwY;nOJzixb+ie)dIk7ZmGw9-I+@@eaG?&Pi81CH z31y(!4e!{zI4xVSkw&ANG-WAWtfHzmp+(4aGdvq!Wha;3&GXyj;;Ui*X8P{!ZuE9! z(#M0V`;U8{&#qp*ermsM58GGs`wo&Zy&rB?(-Irhy6mg%Nh7P>Ht?$!d)7Rr;E&V2 zdH2)_C!d|&^wZIB|LNd4y#;&IAh;`w!IT^irJA5VuwWOISl5FmmQ+k|Z&XZ4c-HGm zAKvPG;Efhh(5*VpIu6VZeab){Q+N%4Z%PfEDo0T16~29WyvHX#)GEDojV`r%%Ur@9 zLTwgc#*F5FWtE<$Z?HhFmI@5yy3V6p1vyJL;W0S7TDwQhQd3>#0O6)kMmZNyD9q_6 zpO#Fuxl&?AUZ0Vawg-z-eNMEtgIIXB(D#50G3}~)?-pM1Mq>w`K13vLJb^ycb7p?Q zy%;3R#!eJx!Om^8+QDLsQ0v)q;nmG5oKL2Gyd}GY=T0} zU&xEet_#o~V%2o!H_=j!C`Pr;Um&m^RYH~F4I!)@RRTHE;oB{K1NZ;)8(%P2pX%k$ zC^ydZ=ZDwN->4MMpY>h8{SJP83;!AVCDWVdi*-)@!+dYry!P;;NY9@UH=h%4eAi^; zr}@6Pc|EMq|2@}lo-a&Bepcz<Aj=)b0n_{Ti|yu$FxpACF2yz%2g`#=4xB5#=Inf|k3+HdMH z&)-A({}$+f!1U&6=&SALVjF*oAJk=2-WNZoQ`XP#=)X<<)&Bn()9>nRjTc|)ALjWD z7fkfOj{mCke}#9%6H|WUCH|m)UaJ2$epNr#|F4e!Um&GO|Kg?2YM$$kaS2~ko_~YC zOZ2-;&rj1{CR?}Py#IcM-h9X1JkR!7fVcotzbWq@D)fKwM>@ZG@^G5$YOL0O;Kx5Q z{ZE*el?nLAcP2qUUzGoB{1g7B{u}x~`5#@sdH!3a!BzhMbA^8S3p$5+e!W+&(D2Q? z{TKY8cR$Gme?8MbzW$(}KhrWf^|MONkY^G<4?q-*`{_GxR@V`X4dw;}3PcjeGt4 zo)IiAu*-x8~Wd@yf@t`J}XUB6hGkl z|2EMDd>fB3*P1|)&{r}m&>-PUPzB6C^$JA@y{W5-RqyOvQ`6-p<%L+l| H&&K}&DLF|Q diff --git a/examples/core.clj b/examples/core.clj new file mode 100644 index 0000000..2b0d7ae --- /dev/null +++ b/examples/core.clj @@ -0,0 +1,8 @@ +(defn square [x] + (* x x)) + +(def numbers [1 2 3 4 5]) + +(println (map square numbers)) +(println (filter (fn [x] (> x 2)) numbers)) +(println (reduce + 0 numbers)) diff --git a/examples/ffi.clj b/examples/ffi.clj new file mode 100644 index 0000000..0ad37d5 --- /dev/null +++ b/examples/ffi.clj @@ -0,0 +1,9 @@ +; C FFI — Достъп до C функции директно от Clojure +; (c-fn sin "math.h" :double [:double]) +; Това генерира Nim proc с {.importc.} pragma + +(println "=== C FFI ===") +; Ползваме Nim interop към math вместо директен C FFI за демонстрация +(println (nim/math/sin 1.5708)) ; sin(pi/2) ≈ 1.0 +(println (nim/math/cos 0.0)) ; cos(0) = 1.0 +(println (nim/math/sqrt 144.0)) ; sqrt(144) = 12.0 diff --git a/examples/interop.clj b/examples/interop.clj new file mode 100644 index 0000000..2f352eb --- /dev/null +++ b/examples/interop.clj @@ -0,0 +1,17 @@ +; Nim Interop — Достъп до Nim стандартната библиотека +(println "=== Nim Math Interop ===") + +; math.sin, math.cos, math.sqrt +(println (nim/math/sin 0.0)) +(println (nim/math/cos 0.0)) +(println (nim/math/sqrt 4.0)) +(println (nim/math/pow 2.0 10.0)) + +(println "=== Nim String Interop ===") + +; strutils functions +(println (nim/strutils/endsWith? "hello world" "world")) +(println (nim/strutils/startsWith? "hello" "hel")) +(println (nim/strutils/toUpper "clojure")) +(println (nim/strutils/toLower "NIM")) +(println (nim/strutils/repeat "ha" 3)) diff --git a/examples/macros.clj b/examples/macros.clj new file mode 100644 index 0000000..df4efbf --- /dev/null +++ b/examples/macros.clj @@ -0,0 +1,33 @@ +; Macro test +(println "=== Threading macros ===") + +; -> +(println (-> 5 (+ 3) (* 2))) +; => (* (+ 5 3) 2) = 16 + +; ->> +(println (->> [1 2 3] (map (fn [x] (* x x))) (reduce + 0))) +; => 14 + +(println "=== defmacro ===") + +(defmacro unless [condition & body] + (list 'if condition nil (cons 'do body))) + +(unless false + (println "unless works!")) + +(unless true + (println "this should NOT print")) + +(println "=== when macro ===") + +(when true + (println "when works!")) + +(println "=== and/or ===") + +(println (and true true)) +(println (and true false)) +(println (or false true)) +(println (or false false)) diff --git a/lib/cljnim_runtime.nim b/lib/cljnim_runtime.nim new file mode 100644 index 0000000..d459929 --- /dev/null +++ b/lib/cljnim_runtime.nim @@ -0,0 +1,1072 @@ +import strutils, sequtils, hashes, algorithm + +type + CljKind* = enum + ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol, + ckList, ckVector, ckMap, ckFn, ckAtom + + CljVal* = ref CljValObj + CljValObj = 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: listItems*: seq[CljVal] + of ckVector: vecData*: seq[CljVal] + of ckMap: + mapKeys*: seq[CljVal] + mapVals*: seq[CljVal] + of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal + of ckAtom: atomVal*: 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 cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64) +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, listItems: items) +proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, vecData: items) +proc cljFn*(p: proc(args: seq[CljVal]): CljVal): CljVal = CljVal(kind: ckFn, fnProc: p) + +proc cljMap*(keys, vals: seq[CljVal]): CljVal = + CljVal(kind: ckMap, mapKeys: keys, mapVals: vals) + +proc cljMapFromPairs*(pairs: seq[(CljVal, CljVal)]): CljVal = + var ks: seq[CljVal] = @[] + var vs: seq[CljVal] = @[] + for (k, v) in pairs: + ks.add(k) + vs.add(v) + cljMap(ks, vs) + +# ---- Equality ---- + +proc `==`*(a, b: CljVal): bool = + if a.isNil and b.isNil: return true + if a.isNil or b.isNil: return false + if a.kind != b.kind: return false + case a.kind + of ckNil: true + of ckBool: a.boolVal == b.boolVal + of ckInt: a.intVal == b.intVal + of ckFloat: a.floatVal == b.floatVal + of ckString: a.strVal == b.strVal + of ckKeyword: a.kwName == b.kwName + of ckSymbol: a.symName == b.symName + else: false + +# ---- Display ---- + +proc cljRepr*(v: CljVal): string = + if v.isNil: return "nil" + case v.kind + of ckNil: "nil" + of ckBool: $v.boolVal + of ckInt: $v.intVal + of ckFloat: $v.floatVal + of ckString: "\"" & v.strVal.replace("\"", "\\\"") & "\"" + of ckKeyword: ":" & v.kwName + of ckSymbol: v.symName + of ckList: "(" & v.listItems.mapIt(cljRepr(it)).join(" ") & ")" + of ckVector: "[" & v.vecData.mapIt(cljRepr(it)).join(" ") & "]" + of ckMap: + var parts: seq[string] = @[] + for i in 0.." + of ckAtom: "(atom " & cljRepr(v.atomVal) & ")" + +proc cljStr*(v: CljVal): string = + if v.isNil: return "" + case v.kind + of ckString: v.strVal + of ckKeyword: ":" & v.kwName + of ckSymbol: v.symName + else: cljRepr(v) + +# ---- Type predicates ---- + +proc cljIsNil*(v: CljVal): bool = v.isNil or v.kind == ckNil +proc cljIsTrue*(v: CljVal): bool = v.kind == ckBool and v.boolVal +proc cljIsFalse*(v: CljVal): bool = v.kind == ckBool and not v.boolVal +proc cljIsTruthy*(v: CljVal): bool = not cljIsNil(v) and not cljIsFalse(v) + +# ---- I/O ---- + +proc cljPrintln*(args: seq[CljVal]): CljVal = + var parts: seq[string] = @[] + for a in args: + parts.add(cljStr(a)) + echo parts.join(" ") + cljNil() + +proc cljPrn*(args: seq[CljVal]): CljVal = + var parts: seq[string] = @[] + for a in args: + parts.add(cljRepr(a)) + echo parts.join(" ") + cljNil() + +# ---- Arithmetic ---- + +proc cljAdd*(args: seq[CljVal]): CljVal = + var sum: int64 = 0 + var sumF: float64 = 0.0 + var isFloat = false + for a in args: + case a.kind + of ckInt: sum += a.intVal + of ckFloat: + sumF += a.floatVal + isFloat = true + else: raise newException(CatchableError, "+ requires numbers") + if isFloat: cljFloat(sum.float64 + sumF) + else: cljInt(sum) + +proc cljMul*(args: seq[CljVal]): CljVal = + var product: int64 = 1 + var productF: float64 = 1.0 + var isFloat = false + for a in args: + case a.kind + of ckInt: product *= a.intVal + of ckFloat: + productF *= a.floatVal + isFloat = true + else: raise newException(CatchableError, "* requires numbers") + if isFloat: cljFloat(product.float64 * productF) + else: cljInt(product) + +proc cljSub*(args: seq[CljVal]): CljVal = + if args.len == 0: raise newException(CatchableError, "- requires at least 1 argument") + if args.len == 1: + case args[0].kind + of ckInt: return cljInt(-args[0].intVal) + of ckFloat: return cljFloat(-args[0].floatVal) + else: raise newException(CatchableError, "- requires numbers") + var r: int64 + var rF: float64 + var isFloat = false + case args[0].kind + of ckInt: r = args[0].intVal + of ckFloat: + rF = args[0].floatVal + isFloat = true + else: raise newException(CatchableError, "- requires numbers") + for i in 1.. result.intVal: result = args[i] + +# ---- Comparison ---- + +proc cljNumEq*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljBool(true) + for i in 1..= b.intVal: return cljBool(false) + elif a.kind == ckFloat and b.kind == ckFloat: + if a.floatVal >= b.floatVal: return cljBool(false) + else: return cljBool(false) + cljBool(true) + +proc cljGt*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljBool(true) + for i in 1.. b.intVal: return cljBool(false) + else: return cljBool(false) + cljBool(true) + +proc cljGe*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljBool(true) + for i in 1.. 0) + of ckFloat: cljBool(v.floatVal > 0.0) + else: raise newException(CatchableError, "pos? requires a number") + +proc cljNeg*(v: CljVal): CljVal = + case v.kind + of ckInt: cljBool(v.intVal < 0) + of ckFloat: cljBool(v.floatVal < 0.0) + else: raise newException(CatchableError, "neg? requires a number") + +proc cljEven*(v: CljVal): CljVal = + if v.kind == ckInt: cljBool(v.intVal mod 2 == 0) + else: raise newException(CatchableError, "even? requires an integer") + +proc cljOdd*(v: CljVal): CljVal = + if v.kind == ckInt: cljBool(v.intVal mod 2 != 0) + else: raise newException(CatchableError, "odd? requires an integer") + +# ---- Collection operations ---- + +proc cljCount*(v: CljVal): int = + if v.isNil: return 0 + case v.kind + of ckList: v.listItems.len + of ckVector: v.vecData.len + of ckMap: v.mapKeys.len + of ckString: v.strVal.len + else: 0 + +proc cljFirst*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v.listItems[0] + of ckVector: + if v.vecData.len == 0: cljNil() + else: v.vecData[0] + else: cljNil() + +proc cljRest*(v: CljVal): CljVal = + if v.isNil: return cljList(@[]) + case v.kind + of ckList: + if v.listItems.len <= 1: cljList(@[]) + else: cljList(v.listItems[1..^1]) + of ckVector: + if v.vecData.len <= 1: cljList(@[]) + else: cljList(v.vecData[1..^1]) + else: cljList(@[]) + +proc cljNext*(v: CljVal): CljVal = + let r = cljRest(v) + if r.kind == ckList and r.listItems.len == 0: + return cljNil() + r + +proc cljLast*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v.listItems[^1] + of ckVector: + if v.vecData.len == 0: cljNil() + else: v.vecData[^1] + else: cljNil() + +proc cljNth*(v: CljVal, n: int): CljVal = + case v.kind + of ckList: + if n < 0 or n >= v.listItems.len: + raise newException(IndexDefect, "nth: index out of range") + v.listItems[n] + of ckVector: + if n < 0 or n >= v.vecData.len: + raise newException(IndexDefect, "nth: index out of range") + v.vecData[n] + else: + raise newException(CatchableError, "nth requires a collection") + +proc cljConj*(coll: CljVal, item: CljVal): CljVal = + if coll.isNil: + return cljList(@[item]) + case coll.kind + of ckList: + var newItems = @[item] + newItems.add(coll.listItems) + cljList(newItems) + of ckVector: + var newItems = coll.vecData + newItems.add(item) + cljVector(newItems) + else: + cljList(@[item]) + +proc cljCons*(item: CljVal, coll: CljVal): CljVal = + if coll.isNil: + return cljList(@[item]) + case coll.kind + of ckList: + var newItems = @[item] + newItems.add(coll.listItems) + cljList(newItems) + of ckVector: + var newItems = @[item] + newItems.add(coll.vecData) + cljList(newItems) + else: + cljList(@[item]) + +proc cljSeq*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v + of ckVector: + if v.vecData.len == 0: cljNil() + else: cljList(v.vecData) + of ckString: + if v.strVal.len == 0: cljNil() + else: + var chars: seq[CljVal] = @[] + for c in v.strVal: + chars.add(cljString($c)) + cljList(chars) + else: cljNil() + +proc cljVec*(v: CljVal): CljVal = + if v.isNil: return cljVector(@[]) + case v.kind + of ckList: cljVector(v.listItems) + of ckVector: v + else: cljVector(@[v]) + +proc cljEmpty*(v: CljVal): CljVal = + cljBool(cljCount(v) == 0) + +proc cljConcat*(args: seq[CljVal]): CljVal = + var items: seq[CljVal] = @[] + for a in args: + if not a.isNil: + case a.kind + of ckList: items.add(a.listItems) + of ckVector: items.add(a.vecData) + else: discard + cljList(items) + +proc cljTake*(n: int, coll: CljVal): CljVal = + if coll.isNil: return cljList(@[]) + case coll.kind + of ckList: cljList(coll.listItems[0..= s.strVal.len: + raise newException(IndexDefect, "subs: index out of range") + cljString(s.strVal[startIdx..^1]) + +proc cljSubsRange*(s: CljVal, startIdx, endIdx: int): CljVal = + if s.kind != ckString: raise newException(CatchableError, "subs requires a string") + if startIdx < 0 or startIdx > endIdx or endIdx > s.strVal.len: + raise newException(IndexDefect, "subs: index out of range") + cljString(s.strVal[startIdx.. [output.nim] Compile to Nim" echo " cljnim run Compile and run" echo " cljnim read Parse and print AST" + echo " cljnim -e '' Evaluate expression" + echo " cljnim -M -e '' Evaluate with full runtime" quit(0) let cmd = args[0] + # Handle -e and -M -e flags + if cmd == "-e" or cmd == "-M": + var code = "" + var idx = 1 + if cmd == "-M" and args.len > 1 and args[1] == "-e": + idx = 2 + elif cmd == "-e": + idx = 1 + else: + stderr.writeLine("Usage: cljnim [-M] -e ''") + quit(1) + if idx >= args.len: + stderr.writeLine("Error: Missing expression after -e") + quit(1) + code = args[idx] + let tmpDir = getTempDir() + let nimPath = tmpDir / "cljnim_expr.nim" + let binPath = tmpDir / "cljnim_expr" + let forms = reader.readAll(code) + let nimCode = emitter.emitProgram(forms) + writeFile(nimPath, nimCode) + let compileResult = nimCompile(nimPath, binPath) + if compileResult != 0: + stderr.writeLine("Compilation failed") + quit(1) + let runResult = execCmd(binPath) + quit(runResult) + case cmd of "compile": if args.len < 2: diff --git a/src/core.nim b/src/core.nim new file mode 100644 index 0000000..280ca4b --- /dev/null +++ b/src/core.nim @@ -0,0 +1,456 @@ +import runtime +import strutils +import sequtils + +# ---- Arithmetic ---- + +proc cljAdd*(args: seq[CljVal]): CljVal = + var sum: int64 = 0 + var sumFloat: float64 = 0.0 + var isFloat = false + for a in args: + case a.kind + of ckInt: sum += a.intVal + of ckFloat: + sumFloat += a.floatVal + isFloat = true + else: raise newException(CatchableError, "+ requires numbers") + if isFloat: + cljFloat(sum.float64 + sumFloat) + else: + cljInt(sum) + +proc cljMul*(args: seq[CljVal]): CljVal = + var product: int64 = 1 + var productFloat: float64 = 1.0 + var isFloat = false + for a in args: + case a.kind + of ckInt: product *= a.intVal + of ckFloat: + productFloat *= a.floatVal + isFloat = true + else: raise newException(CatchableError, "* requires numbers") + if isFloat: + cljFloat(product.float64 * productFloat) + else: + cljInt(product) + +proc cljSub*(args: seq[CljVal]): CljVal = + if args.len == 0: raise newException(CatchableError, "- requires at least 1 argument") + if args.len == 1: + case args[0].kind + of ckInt: return cljInt(-args[0].intVal) + of ckFloat: return cljFloat(-args[0].floatVal) + else: raise newException(CatchableError, "- requires numbers") + var result: int64 + var resultF: float64 + var isFloat = false + case args[0].kind + of ckInt: result = args[0].intVal + of ckFloat: + resultF = args[0].floatVal + isFloat = true + else: raise newException(CatchableError, "- requires numbers") + for i in 1..= b.intVal: return cljBool(false) + elif a.kind == ckFloat and b.kind == ckFloat: + if a.floatVal >= b.floatVal: return cljBool(false) + else: + return cljBool(false) + cljBool(true) + +proc cljGt*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljBool(true) + for i in 1.. b.intVal: return cljBool(false) + else: + return cljBool(false) + cljBool(true) + +proc cljGe*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljBool(true) + for i in 1.. 0) + of ckFloat: cljBool(v.floatVal > 0.0) + else: raise newException(CatchableError, "pos? requires a number") + +proc cljNeg*(v: CljVal): CljVal = + case v.kind + of ckInt: cljBool(v.intVal < 0) + of ckFloat: cljBool(v.floatVal < 0.0) + else: raise newException(CatchableError, "neg? requires a number") + +proc cljEven*(v: CljVal): CljVal = + if v.kind == ckInt: cljBool(v.intVal mod 2 == 0) + else: raise newException(CatchableError, "even? requires an integer") + +proc cljOdd*(v: CljVal): CljVal = + if v.kind == ckInt: cljBool(v.intVal mod 2 != 0) + else: raise newException(CatchableError, "odd? requires an integer") + +# ---- Collection operations ---- + +proc cljFirst*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v.listItems[0] + of ckVector: + if v.listItems.len == 0: cljNil() + else: v.listItems[0] + else: cljNil() + +proc cljRest*(v: CljVal): CljVal = + if v.isNil: return cljList(@[]) + case v.kind + of ckList: + if v.listItems.len <= 1: cljList(@[]) + else: cljList(v.listItems[1..^1]) + of ckVector: + if v.listItems.len <= 1: cljList(@[]) + else: cljList(v.listItems[1..^1]) + else: cljList(@[]) + +proc cljNext*(v: CljVal): CljVal = + let r = cljRest(v) + if r.kind == ckList and r.listItems.len == 0: + return cljNil() + r + +proc cljLast*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v.listItems[^1] + of ckVector: + if v.listItems.len == 0: cljNil() + else: v.listItems[^1] + else: cljNil() + +proc cljNth*(v: CljVal, n: int): CljVal = + case v.kind + of ckList: + if n < 0 or n >= v.listItems.len: + raise newException(IndexDefect, "nth: index out of range") + v.listItems[n] + of ckVector: + if n < 0 or n >= v.listItems.len: + raise newException(IndexDefect, "nth: index out of range") + v.listItems[n] + else: + raise newException(CatchableError, "nth requires a collection") + +proc cljCount*(v: CljVal): int = + if v.isNil: return 0 + case v.kind + of ckList: v.listItems.len + of ckVector: v.listItems.len + of ckString: v.strVal.len + else: 0 + +proc cljConj*(coll: CljVal, item: CljVal): CljVal = + if coll.isNil: + return cljList(@[item]) + case coll.kind + of ckList: + var newItems = @[item] + newItems.add(coll.listItems) + cljList(newItems) + of ckVector: + var newItems = coll.listItems + newItems.add(item) + cljList(newItems) + else: + cljList(@[item]) + +proc cljCons*(item: CljVal, coll: CljVal): CljVal = + if coll.isNil or (coll.kind == ckList and coll.listItems.len == 0): + return cljList(@[item]) + case coll.kind + of ckList: + var newItems = @[item] + newItems.add(coll.listItems) + cljList(newItems) + of ckVector: + var newItems = @[item] + newItems.add(coll.listItems) + cljList(newItems) + else: + cljList(@[item]) + +proc cljSeq*(v: CljVal): CljVal = + if v.isNil: return cljNil() + case v.kind + of ckList: + if v.listItems.len == 0: cljNil() + else: v + of ckVector: + if v.listItems.len == 0: cljNil() + else: cljList(v.listItems) + of ckString: + if v.strVal.len == 0: cljNil() + else: + var chars: seq[CljVal] = @[] + for c in v.strVal: + chars.add(cljString($c)) + cljList(chars) + else: cljNil() + +proc cljVec*(v: CljVal): CljVal = + if v.isNil: return cljList(@[]) + case v.kind + of ckList: cljList(v.listItems) + of ckVector: v + else: cljList(@[v]) + +proc cljEmpty*(v: CljVal): CljVal = + cljBool(cljCount(v) == 0) + +# ---- Higher-order functions ---- + +proc cljMap*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): seq[CljVal] = + result = @[] + for item in coll: + result.add(f(@[item])) + +proc cljFilter*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): seq[CljVal] = + result = @[] + for item in coll: + let r = f(@[item]) + if r.kind == ckBool and r.boolVal: + result.add(item) + +proc cljReduce*(f: proc(args: seq[CljVal]): CljVal, init: CljVal, coll: seq[CljVal]): CljVal = + result = init + for item in coll: + result = f(@[result, item]) + +proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal = + cljList(cljMap(f, coll)) + +proc cljSome*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal = + for item in coll: + let r = f(@[item]) + if r.kind != ckBool or r.boolVal: + return r + cljNil() + +proc cljEvery*(f: proc(args: seq[CljVal]): CljVal, coll: seq[CljVal]): CljVal = + for item in coll: + let r = f(@[item]) + if r.kind == ckBool and not r.boolVal: + return cljBool(false) + cljBool(true) + +proc cljNot*(v: CljVal): CljVal = + if v.kind == ckBool and not v.boolVal: + cljBool(true) + else: + cljBool(false) + +proc cljApply*(f: proc(args: seq[CljVal]): CljVal, args: seq[CljVal]): CljVal = + f(args) + +proc cljComp*(fns: seq[proc(args: seq[CljVal]): CljVal]): proc(args: seq[CljVal]): CljVal = + proc(args: seq[CljVal]): CljVal = + result = args + for i in countdown(fns.len - 1, 0): + result = fns[i](@[result]) + +proc cljPartial*(f: proc(args: seq[CljVal]): CljVal, partialArgs: seq[CljVal]): proc(args: seq[CljVal]): CljVal = + proc(args: seq[CljVal]): CljVal = + var allArgs = partialArgs + allArgs.add(args) + f(allArgs) + +# ---- I/O ---- + +proc cljPrintln*(args: seq[CljVal]): CljVal = + var parts: seq[string] = @[] + for a in args: + parts.add(cljStr(a)) + echo parts.join(" ") + cljNil() + +proc cljPrn*(args: seq[CljVal]): CljVal = + var parts: seq[string] = @[] + for a in args: + parts.add(cljRepr(a)) + echo parts.join(" ") + cljNil() + +proc cljStr*(args: seq[CljVal]): CljVal = + var s = "" + for a in args: + s.add(cljStr(a)) + cljString(s) + +proc cljPrStr*(args: seq[CljVal]): CljVal = + var parts: seq[string] = @[] + for a in args: + parts.add(cljRepr(a)) + cljString(parts.join(" ")) + +# ---- Misc ---- + +proc cljIdentity*(args: seq[CljVal]): CljVal = + if args.len == 0: cljNil() + else: args[0] + +proc cljConstantly*(v: CljVal): proc(args: seq[CljVal]): CljVal = + proc(args: seq[CljVal]): CljVal = v + +proc cljType*(v: CljVal): CljVal = + if v.isNil: return cljKeyword("nil") + case v.kind + of ckBool: cljKeyword("boolean") + of ckInt: cljKeyword("integer") + of ckFloat: cljKeyword("float") + of ckString: cljKeyword("string") + of ckKeyword: cljKeyword("keyword") + of ckSymbol: cljKeyword("symbol") + of ckList: cljKeyword("list") + of ckVector: cljKeyword("vector") + of ckMap: cljKeyword("map") + of ckFn: cljKeyword("function") + of ckAtom: cljKeyword("atom") + +proc cljMinMax*(args: seq[CljVal], isMin: bool): CljVal = + if args.len == 0: raise newException(CatchableError, "min/max requires at least 1 argument") + result = args[0] + for i in 1.. result.intVal: result = args[i] + +proc cljAbs*(v: CljVal): CljVal = + case v.kind + of ckInt: cljInt(abs(v.intVal)) + of ckFloat: cljFloat(abs(v.floatVal)) + else: raise newException(CatchableError, "abs requires a number") + +proc cljQuot*(a, b: CljVal): CljVal = + if a.kind == ckInt and b.kind == ckInt: + cljInt(a.intVal div b.intVal) + else: + raise newException(CatchableError, "quot requires integers") diff --git a/src/emitter.nim b/src/emitter.nim index 54bb39e..7f31431 100644 --- a/src/emitter.nim +++ b/src/emitter.nim @@ -1,73 +1,243 @@ # Clojure → Nim Emitter -import strutils, sequtils +import strutils, sequtils, sets import types +import macros + +var requiredImports* = initHashSet[string]() 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("_STAR_") + of '+': result.add("_PLUS_") + of '/': result.add("_SLASH_") + of '=': result.add("_EQ_") + of '>': result.add("_GT_") + of '<': result.add("_LT_") of '.': result.add('_') - of '\'': result.add("_QUOTE") + of '\'': result.add("_QUOTE_") + of '&': result.add("_AMP_") else: result.add(c) proc emitExpr*(v: CljVal, indent: int = 0): string +proc emitBlock*(items: seq[CljVal], indent: int): 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 indentStr(indent: int): string = + " ".repeat(indent) + +proc runtimeName(op: string): string = + case op + of "+": "cljAdd" + of "-": "cljSub" + of "*": "cljMul" + of "/": "cljDiv" + of "=": "cljNumEq" + of ">": "cljGt" + of "<": "cljLt" + of ">=": "cljGe" + of "<=": "cljLe" + of "not=": "cljNotEq" + of "not": "cljNot" + of "println": "cljPrintln" + of "prn": "cljPrn" + of "str": "cljStrConcat" + of "pr-str": "cljPrStrConcat" + of "inc": "cljInc" + of "dec": "cljDec" + of "zero?": "cljZero" + of "pos?": "cljPos" + of "neg?": "cljNeg" + of "even?": "cljEven" + of "odd?": "cljOdd" + of "first": "cljFirst" + of "rest": "cljRest" + of "next": "cljNext" + of "last": "cljLast" + of "nth": "cljNth" + of "count": "cljCount" + of "conj": "cljConj" + of "cons": "cljCons" + of "seq": "cljSeq" + of "vec": "cljVec" + of "empty?": "cljEmpty" + of "concat": "cljConcat" + of "take": "cljTake" + of "drop": "cljDrop" + of "reverse": "cljReverse" + of "sort": "cljSort" + of "distinct": "cljDistinct" + of "flatten": "cljFlatten" + of "partition": "cljPartition" + of "frequencies": "cljFrequencies" + of "get": "cljGet" + of "get-in": "cljGetIn" + of "assoc": "cljAssoc" + of "dissoc": "cljDissoc" + of "keys": "cljKeys" + of "vals": "cljVals" + of "contains?": "cljContains" + of "select-keys": "cljSelectKeys" + of "merge": "cljMerge" + of "identity": "cljIdentity" + of "type": "cljType" + of "abs": "cljAbs" + of "mod": "cljMod" + of "min": "cljMin" + of "max": "cljMax" + of "apply": "cljApply" + of "atom": "cljAtom" + of "deref": "cljDeref" + of "reset!": "cljReset" + of "swap!": "cljSwap" + else: "" + +proc emitArgs(args: seq[CljVal]): string = + var parts: seq[string] = @[] + for a in args: + parts.add(emitExpr(a, 0)) + return parts.join(", ") proc emitSpecialForm(items: seq[CljVal], indent: int): string = - let spaces = " ".repeat(indent) + let sp = indentStr(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 - + + # ---- Special forms (language constructs) ---- 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.. arg value + var env: seq[(string, CljVal)] = @[] + if macroParams.kind == ckVector: + for i in 0.. 0: - return spaces & "proc " & procName & "(" & typedParams.join(", ") & "): int =\n" & bodyCode + if p.kind != ckSymbol: + raise newException(EmitterError, "defn- params must be symbols") + paramNames.add(mangleName(p.symName) & ": CljVal") + let body = items[3..^1] + var bodyCode = "" + if body.len == 1: + bodyCode = emitExpr(body[0], indent + 1) else: - return spaces & "proc " & procName & "(): int =\n" & bodyCode - + bodyCode = emitBlock(body, indent + 1) + let procName = mangleName(name.symName) + return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode + of "fn": if items.len < 3: raise newException(EmitterError, "fn requires params and body") @@ -107,21 +294,15 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = for p in params.items: if p.kind != ckSymbol: raise newException(EmitterError, "fn params must be symbols") - paramNames.add(mangleName(p.symName) & ": auto") + paramNames.add(mangleName(p.symName) & ": CljVal") 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 - + return sp & "(proc(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode & ")" + of "let": if items.len < 3: raise newException(EmitterError, "let requires bindings and body") @@ -131,142 +312,649 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = 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: + lines.add(sp & "block:") + var bi = 0 + while bi < bindings.items.len: + let bname = bindings.items[bi] + let bval = bindings.items[bi+1] + if bname.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) + lines.add(indentStr(indent + 1) & "let " & mangleName(bname.symName) & " = " & emitExpr(bval, 0)) + bi += 2 let body = items[2..^1] for j, b in body: - let bcode = emitExpr(b, indent + 1).strip() + let bcode = emitExpr(b, indent + 1) if j == body.len - 1: - lines.add(" ".repeat(indent + 1) & bcode) + lines.add(bcode) else: - if bcode.startsWith("echo ") or bcode.startsWith("discard "): - lines.add(" ".repeat(indent + 1) & bcode) + let stripped = bcode.strip() + if stripped.startsWith("echo ") or stripped.startsWith("discard ") or + stripped.startsWith("block:") or stripped.startsWith("if ") or + stripped.startsWith("try:"): + lines.add(bcode) else: - lines.add(" ".repeat(indent + 1) & "discard " & bcode) - return spaces & lines.join("\n") - + lines.add(indentStr(indent + 1) & "discard " & stripped) + return 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 condCode = emitExpr(items[1], 0) let thenCode = emitExpr(items[2], indent + 1) - var result = spaces & "if " & condCode & ":\n" & " ".repeat(indent + 1) & thenCode + let thenStripped = thenCode.strip() + var thenFinal = thenCode + if not (thenStripped.startsWith("echo ") or thenStripped.startsWith("discard ") or + thenStripped.startsWith("block:") or thenStripped.startsWith("if ") or + thenStripped.startsWith("try:") or thenStripped.startsWith("var ") or + thenStripped.startsWith("let ") or thenStripped.startsWith("proc ")): + thenFinal = indentStr(indent + 1) & "discard " & thenStripped + var ifBlock = sp & "if cljIsTruthy(" & condCode & "):\n" & thenFinal if items.len == 4: let elseCode = emitExpr(items[3], indent + 1) - result.add("\n" & spaces & "else:\n" & " ".repeat(indent + 1) & elseCode) - return result - + let elseStripped = elseCode.strip() + var elseFinal = elseCode + if not (elseStripped.startsWith("echo ") or elseStripped.startsWith("discard ") or + elseStripped.startsWith("block:") or elseStripped.startsWith("if ") or + elseStripped.startsWith("try:") or elseStripped.startsWith("var ") or + elseStripped.startsWith("let ") or elseStripped.startsWith("proc ")): + elseFinal = indentStr(indent + 1) & "discard " & elseStripped + ifBlock.add("\n" & sp & "else:\n" & elseFinal) + return ifBlock + + of "when": + if items.len < 3: + raise newException(EmitterError, "when requires condition and body") + let condCode = emitExpr(items[1], 0) + var lines: seq[string] = @[] + lines.add(sp & "if cljIsTruthy(" & condCode & "):") + let body = items[2..^1] + for b in body: + lines.add(emitExpr(b, indent + 1)) + return lines.join("\n") + + of "when-let": + if items.len < 4: + raise newException(EmitterError, "when-let requires binding vector and body") + let bindings = items[1] + if bindings.kind != ckVector or bindings.items.len != 2: + raise newException(EmitterError, "when-let binding must be a vector of [name val]") + let bindName = bindings.items[0] + let bindVal = bindings.items[1] + if bindName.kind != ckSymbol: + raise newException(EmitterError, "when-let binding name must be a symbol") + let mangled = mangleName(bindName.symName) + var lines: seq[string] = @[] + lines.add(sp & "block:") + lines.add(indentStr(indent + 1) & "let " & mangled & " = " & emitExpr(bindVal, 0)) + lines.add(indentStr(indent + 1) & "if not cljIsNil(" & mangled & "):") + let body = items[2..^1] + for b in body: + lines.add(emitExpr(b, indent + 2)) + return lines.join("\n") + + of "if-let": + if items.len < 4: + raise newException(EmitterError, "if-let requires binding vector, then, and optional else") + let bindings = items[1] + if bindings.kind != ckVector or bindings.items.len != 2: + raise newException(EmitterError, "if-let binding must be a vector of [name val]") + let bindName = bindings.items[0] + let bindVal = bindings.items[1] + if bindName.kind != ckSymbol: + raise newException(EmitterError, "if-let binding name must be a symbol") + let mangled = mangleName(bindName.symName) + var lines: seq[string] = @[] + lines.add(sp & "block:") + lines.add(indentStr(indent + 1) & "let " & mangled & " = " & emitExpr(bindVal, 0)) + lines.add(indentStr(indent + 1) & "if not cljIsNil(" & mangled & "):") + lines.add(emitExpr(items[2], indent + 2)) + if items.len == 4: + lines.add(indentStr(indent + 1) & "else:") + lines.add(emitExpr(items[3], indent + 2)) + return lines.join("\n") + + of "cond": + if items.len < 3 or items.len mod 2 != 1: + raise newException(EmitterError, "cond requires pairs of test/expr") + var lines: seq[string] = @[] + var first = true + var ci = 1 + while ci < items.len: + let testExpr = items[ci] + let thenExpr = items[ci+1] + if (testExpr.kind == ckSymbol and testExpr.symName == ":else") or + (testExpr.kind == ckKeyword and testExpr.kwName == "else"): + lines.add(sp & "else:") + lines.add(emitExpr(thenExpr, indent + 1)) + elif first: + lines.add(sp & "if cljIsTruthy(" & emitExpr(testExpr, 0) & "):") + lines.add(emitExpr(thenExpr, indent + 1)) + first = false + else: + lines.add(sp & "elif cljIsTruthy(" & emitExpr(testExpr, 0) & "):") + lines.add(emitExpr(thenExpr, indent + 1)) + ci += 2 + return lines.join("\n") + + of "loop": + if items.len < 3: + raise newException(EmitterError, "loop requires bindings and body") + let bindings = items[1] + if bindings.kind != ckVector: + raise newException(EmitterError, "loop bindings must be a vector") + if bindings.items.len mod 2 != 0: + raise newException(EmitterError, "loop bindings must have even number of elements") + var loopParams: seq[(string, string)] = @[] + var li = 0 + while li < bindings.items.len: + let lname = bindings.items[li] + let lval = bindings.items[li+1] + if lname.kind != ckSymbol: + raise newException(EmitterError, "loop binding name must be a symbol") + loopParams.add((mangleName(lname.symName), emitExpr(lval, 0))) + li += 2 + var lines: seq[string] = @[] + for (lpName, lpVal) in loopParams: + lines.add(sp & "var " & lpName & ": CljVal = " & lpVal) + lines.add(sp & "while true:") + let body = items[2..^1] + for b in body: + lines.add(emitExpr(b, indent + 1)) + return lines.join("\n") + + of "recur": + if items.len < 2: + raise newException(EmitterError, "recur requires arguments") + # Simplified: emit as discard (real impl needs loop var assignment) + var lines: seq[string] = @[] + for ri in 1.. 0 and form.items[0].kind == ckSymbol: + if form.items[0].symName == "catch": + if form.items.len < 3: + raise newException(EmitterError, "catch requires exception type, name, and body") + let exName = form.items[2] + if exName.kind != ckSymbol: + raise newException(EmitterError, "catch name must be a symbol") + catchClauses.add((mangleName(exName.symName), form.items[3..^1])) + elif form.items[0].symName == "finally": + finallyBody = form.items[1..^1] + else: + bodyForms.add(form) + else: + bodyForms.add(form) + inc ti + var lines: seq[string] = @[] + lines.add(sp & "try:") + for b in bodyForms: + lines.add(emitExpr(b, indent + 1)) + for (exVar, exBody) in catchClauses: + lines.add(sp & "except CatchableError:") + lines.add(indentStr(indent + 1) & "let " & exVar & " = cljString(getCurrentExceptionMsg())") + for eb in exBody: + lines.add(emitExpr(eb, indent + 1)) + if finallyBody.len > 0: + lines.add(sp & "finally:") + for fb in finallyBody: + lines.add(emitExpr(fb, indent + 1)) + return lines.join("\n") + + of "throw": + if items.len != 2: + raise newException(EmitterError, "throw requires exactly 1 argument") + return sp & "raise newException(CatchableError, cljStr(" & emitExpr(items[1], 0) & "))" + 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 & "\")" + return sp & "cljSymbol(\"" & quoted.symName & "\")" of ckList: var parts: seq[string] = @[] for item in quoted.items: - parts.add(emitExpr(item, indent)) - return spaces & "cljList(@[" & parts.join(", ") & "])" + parts.add(emitExpr(item, 0)) + return sp & "cljList(@[" & parts.join(", ") & "])" of ckVector: var parts: seq[string] = @[] for item in quoted.items: - parts.add(emitExpr(item, indent)) - return spaces & "cljVector(@[" & parts.join(", ") & "])" + parts.add(emitExpr(item, 0)) + return sp & "cljVector(@[" & parts.join(", ") & "])" else: return emitExpr(quoted, indent) - + + of "map": + if items.len != 3: + raise newException(EmitterError, "map requires exactly 2 arguments (f, coll)") + let fnArg = items[1] + let collArg = emitExpr(items[2], 0) + if fnArg.kind == ckSymbol: + let rn = runtimeName(fnArg.symName) + if rn.len > 0: + return sp & "cljMap(cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args)), " & 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 == "fn": + # Inline fn: (map (fn [x] ...) coll) + let fnParams = fnArg.items[1] + let fnBody = fnArg.items[2..^1] + var pNames: seq[string] = @[] + for p in fnParams.items: + pNames.add(mangleName(p.symName)) + var bodyExpr = "" + if fnBody.len == 1: + bodyExpr = emitExpr(fnBody[0], 0).strip() + else: + var parts: seq[string] = @[] + for b in fnBody: + parts.add(emitExpr(b, 0).strip()) + bodyExpr = parts.join("; ") + let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")" + return sp & "cljMap(cljFn(" & fnProc & "), " & collArg & ")" + else: + return sp & "cljMap(" & emitExpr(fnArg, 0) & ", " & collArg & ")" + + of "filter": + if items.len != 3: + raise newException(EmitterError, "filter requires exactly 2 arguments") + let fnArg = items[1] + let collArg = emitExpr(items[2], 0) + if fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName == "fn": + let fnParams = fnArg.items[1] + let fnBody = fnArg.items[2..^1] + var pNames: seq[string] = @[] + for p in fnParams.items: + pNames.add(mangleName(p.symName)) + var bodyExpr = "" + if fnBody.len == 1: + bodyExpr = emitExpr(fnBody[0], 0).strip() + else: + var parts: seq[string] = @[] + for b in fnBody: + parts.add(emitExpr(b, 0).strip()) + bodyExpr = parts.join("; ") + # Wrap single-param fn as seq[CljVal] version + let fnProc = "proc(args: seq[CljVal]): CljVal = (let " & pNames[0] & " = args[0]; " & bodyExpr & ")" + return sp & "cljFilter(cljFn(" & fnProc & "), " & collArg & ")" + else: + return sp & "cljFilter(" & emitExpr(fnArg, 0) & ", " & collArg & ")" + + of "reduce": + if items.len < 3 or items.len > 4: + raise newException(EmitterError, "reduce requires 2 or 3 arguments") + if items.len == 3: + # (reduce f coll) — first element as init + let fnArg = items[1] + let collArg = emitExpr(items[2], 0) + return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", cljNil(), " & collArg & ")" + else: + # (reduce f init coll) + let fnArg = items[1] + let initArg = emitExpr(items[2], 0) + let collArg = emitExpr(items[3], 0) + return sp & "cljReduce(" & emitExpr(fnArg, 0) & ", " & initArg & ", " & collArg & ")" + + of "mapv": + if items.len != 3: + raise newException(EmitterError, "mapv requires exactly 2 arguments") + return sp & "cljMapv(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + + of "apply": + if items.len != 3: + raise newException(EmitterError, "apply requires exactly 2 arguments") + return sp & "cljApply(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + + of "some": + if items.len != 3: + raise newException(EmitterError, "some requires exactly 2 arguments") + return sp & "cljSome(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + + of "every?": + if items.len != 3: + raise newException(EmitterError, "every? requires exactly 2 arguments") + return sp & "cljEvery(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + + of "into": + if items.len != 3: + raise newException(EmitterError, "into requires exactly 2 arguments") + return sp & "cljInto(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + + of "comp": + var parts: seq[string] = @[] + for i in 1.. 4: + raise newException(EmitterError, "subs requires 2 or 3 arguments") + if items.len == 3: + return sp & "cljSubs(" & emitExpr(items[1], 0) & ", cljInt(" & emitExpr(items[2], 0) & ").intVal)" + else: + return sp & "cljSubsRange(" & emitExpr(items[1], 0) & ", cljInt(" & emitExpr(items[2], 0) & ").intVal, cljInt(" & emitExpr(items[3], 0) & ").intVal)" + + of "get-in": + if items.len < 3 or items.len > 4: + raise newException(EmitterError, "get-in requires 2 or 3 arguments") + if items.len == 3: + return sp & "cljGetIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + else: + return sp & "cljGetIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")" + + of "update": + if items.len < 4: + raise newException(EmitterError, "update requires at least 3 arguments") + var fArg: string + if items[3].kind == ckSymbol: + let rn = runtimeName(items[3].symName) + if rn.len > 0: + fArg = "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))" + else: + fArg = mangleName(items[3].symName) + else: + fArg = emitExpr(items[3], 0) + var extraArgs: seq[string] = @[] + for i in 4.. 0: + return sp & "cljUpdate(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & fArg & ", @[" & extraArgs.join(", ") & "])" + else: + return sp & "cljUpdate(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & fArg & ")" + + of "assoc-in": + if items.len != 4: + raise newException(EmitterError, "assoc-in requires exactly 3 arguments") + return sp & "cljAssocIn(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ", " & emitExpr(items[3], 0) & ")" + + of "and": + if items.len < 2: return sp & "cljBool(true)" + if items.len == 2: return emitExpr(items[1], indent) + # (and a b c) => ternary: if a then (if b then c else false) else false + # Generate as nested inline if + let lastExpr = emitExpr(items[^1], 0) + var r = lastExpr + for i in countdown(items.len - 2, 1): + r = "(if cljIsTruthy(" & emitExpr(items[i], 0) & "): " & r & " else: cljBool(false))" + return sp & r + + of "or": + if items.len < 2: return sp & "cljNil()" + if items.len == 2: return emitExpr(items[1], indent) + # (or a b c) => ternary: if a then a else (if b then b else c) + let lastExpr = emitExpr(items[^1], 0) + var r = lastExpr + for i in countdown(items.len - 2, 1): + let argCode = emitExpr(items[i], 0) + r = "(if cljIsTruthy(" & argCode & "): " & argCode & " else: " & r & ")" + return sp & r + + of "nil?": + if items.len != 2: + raise newException(EmitterError, "nil? requires exactly 1 argument") + return sp & "cljBool(cljIsNil(" & emitExpr(items[1], 0) & "))" + + of "set!": + if items.len != 3: + raise newException(EmitterError, "set! requires exactly 2 arguments") + let target = items[1] + if target.kind != ckSymbol: + raise newException(EmitterError, "set! target must be a symbol") + return sp & mangleName(target.symName) & " = " & emitExpr(items[2], 0) + + of "range": + if items.len < 1 or items.len > 3: + raise newException(EmitterError, "range requires 0, 1, or 2 arguments") + if items.len == 1: + return sp & "cljList(@[])" + elif items.len == 2: + return sp & "cljRange(" & emitExpr(items[1], 0) & ")" + else: + return sp & "cljRange(" & emitExpr(items[1], 0) & ", " & emitExpr(items[2], 0) & ")" + else: - # Function call + # ---- Nim interop: nim/module/function ---- + if op.startsWith("nim/") or op.startsWith("nim."): + let parts = op.replace(".", "/").split("/") + if parts.len >= 3: + let module = parts[1] + var funcChain = parts[2..^1].join(".") + # Strip ? suffix (Clojure convention) — not valid in Nim + if funcChain.endsWith("?"): + funcChain = funcChain[0..^2] + var argParts: seq[string] = @[] + for i in 1..= 1: + var nimArgs: seq[string] = @[] + nimArgs.add(argParts[0] & ".strVal") + for i in 1..= 1: + var nimArgs: seq[string] = @[] + nimArgs.add(argParts[0] & ".strVal") + for i in 1..= 2: + var nimArgs: seq[string] = @[] + nimArgs.add(argParts[0] & ".strVal") + for i in 1..= 2: + return sp & "cljList(" & funcChain & "(" & argParts[0] & ".strVal, " & argParts[1] & ".strVal).mapIt(cljString(it)))" + return sp & funcChain & "(" & argParts.join(", ") & ")" + of "times": + requiredImports.incl("times") + return sp & funcChain & "(" & argParts.join(", ") & ")" + of "os": + requiredImports.incl("os") + return sp & "cljString(" & funcChain & "(" & argParts.join(", ") & "))" + of "system": + return sp & funcChain & "(" & argParts.join(", ") & ")" + else: + requiredImports.incl(module) + return sp & funcChain & "(" & argParts.join(", ") & ")" + elif parts.len == 2: + return sp & "cljString(\"" & parts[1] & "\")" + + # ---- C FFI import: (c/import "header" :fn1 :fn2) ---- + if op == "c/import" or op == "c-ffi/import": + if items.len < 2: + raise newException(EmitterError, "c/import requires at least a header path") + let header = items[1] + if header.kind != ckString: + raise newException(EmitterError, "c/import header must be a string") + let headerPath = header.strVal + var ffiProcs: seq[string] = @[] + for i in 2.. 0: + var argParts: seq[string] = @[] + for i in 1..", "<", ">=", "<=", "not=", + "println", "prn", "print", "str", "pr-str", + "concat", "min", "max", "merge"] + var call: string + if variadic and argParts.len > 0: + call = rn & "(@[" & argParts.join(", ") & "])" + else: + call = rn & "(" & argParts.join(", ") & ")" + if op in ["println", "prn", "print"]: + return sp & "discard " & call + return sp & call + + # ---- User-defined function call ---- var args: seq[string] = @[] for i in 1.. 0: + # Known function symbol — emit as runtime fn reference + return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))" + return sp & mangleName(v.symName) of ckList: if v.items.len == 0: - return spaces & "cljList(@[])" - return emitSpecialForm(v.items, indent) + return sp & "cljList(@[])" + # Macro-expand before emitting + let expanded = macroexpand(v) + if expanded.kind != ckList or expanded == v: + return emitSpecialForm(v.items, indent) + return emitExpr(expanded, indent) of ckVector: var parts: seq[string] = @[] for item in v.items: - parts.add(emitExpr(item, indent)) - return spaces & "@[" & parts.join(", ") & "]" + parts.add(emitExpr(item, 0)) + return sp & "cljVector(@[" & parts.join(", ") & "])" of ckMap: - raise newException(EmitterError, "Maps not yet supported in emitter") + if v.mapKeys.len == 0: + return sp & "cljMap(@[], @[])" + var keyParts: seq[string] = @[] + var valParts: seq[string] = @[] + for i in 0.. 0 and form.items[0].kind == ckSymbol and - form.items[0].symName in ["def", "defn"] + form.items[0].symName in ["def", "defn", "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:"): + let code = emitExpr(form, 2) + let stripped = code.strip() + if stripped.startsWith("echo ") or stripped.startsWith("discard ") or + stripped.startsWith("block:") or stripped.startsWith("if ") or + stripped.startsWith("try:") or stripped.startsWith("var "): mainForms.add(code) elif i == forms.len - 1: - # Last form - echo its result - mainForms.add("echo " & code) + mainForms.add(indentStr(2) & "echo " & stripped) else: - mainForms.add("discard " & code) - + mainForms.add(indentStr(2) & "discard " & stripped) + var lines = headerLines + # Add collected Nim imports + for imp in requiredImports: + lines.add("import " & imp) + lines.add("") lines.add(defs) - + if mainForms.len > 0: lines.add("") lines.add("when isMainModule:") for form in mainForms: - lines.add(" " & form) - + lines.add(form) + return lines.join("\n") & "\n" diff --git a/src/macros.nim b/src/macros.nim new file mode 100644 index 0000000..f7047cc --- /dev/null +++ b/src/macros.nim @@ -0,0 +1,468 @@ +import tables +import types + +type + MacroFn* = proc(args: seq[CljVal]): CljVal + +var macroTable* = initTable[string, MacroFn]() + +proc defineMacro*(name: string, fn: MacroFn) = + macroTable[name] = fn + +proc isMacro*(name: string): bool = + macroTable.hasKey(name) + +proc expandMacro*(name: string, args: seq[CljVal]): CljVal = + macroTable[name](args) + +proc expandSyntaxQuote*(form: CljVal): CljVal +proc macroexpand1*(form: CljVal): CljVal +proc macroexpand*(form: CljVal): CljVal + +proc gensymCounter*: int = + var counter {.global.} = 0 + inc counter + counter + +proc gensymName*(prefix: string = "G__"): string = + prefix & $gensymCounter() + +proc expandSyntaxQuote*(form: CljVal): CljVal = + if form.isNil: return cljNil() + case form.kind + of ckSymbol: + return cljList(@[cljSymbol("quote"), form]) + of ckKeyword: + return cljList(@[cljSymbol("quote"), form]) + of ckList: + if form.items.len == 0: + return cljList(@[cljSymbol("quote"), cljList(@[])]) + let head = form.items[0] + if head.kind == ckSymbol: + if head.symName == "unquote": + if form.items.len != 2: + raise newException(CatchableError, "unquote requires exactly 1 argument") + return form.items[1] + if head.symName == "unquote-splicing": + raise newException(CatchableError, "unquote-splicing can only be used inside a collection") + # Process each element + var result: seq[CljVal] = @[] + var parts: seq[CljVal] = @[] + for item in form.items: + if item.kind == ckList and item.items.len == 2 and + item.items[0].kind == ckSymbol and item.items[0].symName == "unquote-splicing": + parts.add(item.items[1]) + elif item.kind == ckList and item.items.len == 2 and + item.items[0].kind == ckSymbol and item.items[0].symName == "unquote": + parts.add(cljList(@[cljSymbol("list"), item.items[1]])) + else: + parts.add(cljList(@[cljSymbol("list"), expandSyntaxQuote(item)])) + if parts.len == 0: + return cljList(@[cljSymbol("quote"), cljList(@[])]) + # Build (concat part1 part2 ...) + if parts.len == 1: + return parts[0] + return cljList(@[cljSymbol("concat")] & parts) + of ckVector: + var parts: seq[CljVal] = @[] + for item in form.items: + if item.kind == ckList and item.items.len == 2 and + item.items[0].kind == ckSymbol and item.items[0].symName == "unquote-splicing": + parts.add(item.items[1]) + else: + parts.add(cljList(@[cljSymbol("list"), expandSyntaxQuote(item)])) + return cljList(@[cljSymbol("vec"), + if parts.len == 0: cljList(@[cljSymbol("quote"), cljList(@[])]) + elif parts.len == 1: parts[0] + else: cljList(@[cljSymbol("concat")] & parts)]) + of ckMap: + # Maps: expand each key and value + return cljList(@[cljSymbol("quote"), form]) + else: + return cljList(@[cljSymbol("quote"), form]) + +proc macroexpand1*(form: CljVal): CljVal = + if form.isNil: return form + if form.kind != ckList or form.items.len == 0: + return form + let head = form.items[0] + if head.kind != ckSymbol: + return form + let name = head.symName + # Handle special forms that should not be macro-expanded + if name in ["def", "fn", "defn", "defn-", "let", "if", "when", "cond", + "do", "loop", "recur", "try", "catch", "finally", "throw", + "quote", "var", "set!", "ns", "defmacro", + "and", "or", "nil?", "range"]: + return form + # Handle syntax-quote + if name == "syntax-quote": + if form.items.len != 2: + raise newException(CatchableError, "syntax-quote requires exactly 1 argument") + return expandSyntaxQuote(form.items[1]) + # Check if it's a macro + if isMacro(name): + return expandMacro(name, form.items[1..^1]) + return form + +proc macroexpand*(form: CljVal): CljVal = + result = form + while true: + let expanded = macroexpand1(result) + if expanded == result: + return result + result = expanded + +# ---- Built-in macro implementations ---- + +proc threadingMacro(args: seq[CljVal], reverse: bool): CljVal = + if args.len < 2: + raise newException(CatchableError, "Threading macro requires at least 2 arguments") + var result = args[0] + for i in 1.. 0: + var newItems = step.items + if reverse: + newItems.add(result) + else: + newItems.insert(result, 1) + result = cljList(newItems) + elif step.kind == ckSymbol: + # (-> x f) => (f x) + result = cljList(@[step, result]) + else: + raise newException(CatchableError, "Threading macro requires list or symbol forms") + result + +proc initBuiltinMacros*() = + # -> + defineMacro("fn*", proc(args: seq[CljVal]): CljVal = + # fn* is used internally by reader for #() + cljList(@[cljSymbol("fn")] & args) + ) + + # -> + defineMacro("->", proc(args: seq[CljVal]): CljVal = + threadingMacro(args, false) + ) + + # ->> + defineMacro("->>", proc(args: seq[CljVal]): CljVal = + threadingMacro(args, true) + ) + + # and + defineMacro("and", proc(args: seq[CljVal]): CljVal = + if args.len == 0: return cljBool(true) + if args.len == 1: return args[0] + # (and a b c) => (if a (if b c false) false) + var r = args[^1] + for i in countdown(args.len - 2, 0): + r = cljList(@[cljSymbol("if"), args[i], r, cljBool(false)]) + r + ) + + # or + defineMacro("or", proc(args: seq[CljVal]): CljVal = + if args.len == 0: return cljNil() + if args.len == 1: return args[0] + # (or a b c) => (if a a (if b b (if c c nil))) + var r = args[^1] + for i in countdown(args.len - 2, 0): + r = cljList(@[cljSymbol("if"), args[i], args[i], r]) + r + ) + + # when-let + defineMacro("when-let", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "when-let requires binding and body") + let binding = args[0] + let body = args[1..^1] + if binding.kind != ckVector or binding.items.len != 2: + raise newException(CatchableError, "when-let binding must be [name val]") + let sym = binding.items[0] + let val = binding.items[1] + let gs = cljSymbol(gensymName("wl_")) + cljList(@[cljSymbol("let"), cljVector(@[gs, val]), + cljList(@[cljSymbol("if"), gs, + cljList(@[cljSymbol("let"), cljVector(@[sym, gs])] & body)])]) + ) + + # if-let + defineMacro("if-let", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "if-let requires binding, then, and optional else") + let binding = args[0] + let thenExpr = args[1] + let elseExpr = if args.len > 2: args[2] else: cljNil() + if binding.kind != ckVector or binding.items.len != 2: + raise newException(CatchableError, "if-let binding must be [name val]") + let sym = binding.items[0] + let val = binding.items[1] + let gs = cljSymbol(gensymName("il_")) + cljList(@[cljSymbol("let"), cljVector(@[gs, val]), + cljList(@[cljSymbol("if"), gs, + cljList(@[cljSymbol("let"), cljVector(@[sym, gs]), thenExpr]), + elseExpr])]) + ) + + # when + defineMacro("when", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "when requires condition and body") + let test = args[0] + let body = args[1..^1] + cljList(@[cljSymbol("if"), test, cljList(@[cljSymbol("do")] & body)]) + ) + + # when-not + defineMacro("when-not", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "when-not requires condition and body") + let test = args[0] + let body = args[1..^1] + cljList(@[cljSymbol("if"), test, cljNil(), cljList(@[cljSymbol("do")] & body)]) + ) + + # cond + defineMacro("cond", proc(args: seq[CljVal]): CljVal = + if args.len == 0: return cljNil() + if args.len mod 2 != 0: + raise newException(CatchableError, "cond requires even number of forms") + let test = args[0] + let then = args[1] + if (test.kind == ckKeyword and test.kwName == "else") or + (test.kind == ckSymbol and test.symName == ":else"): + return then + let rest = args[2..^1] + if rest.len == 0: + return cljList(@[cljSymbol("if"), test, then]) + cljList(@[cljSymbol("if"), test, then, + cljList(@[cljSymbol("cond")] & rest)]) + ) + + # cond-> + defineMacro("cond->", proc(args: seq[CljVal]): CljVal = + if args.len < 1: + raise newException(CatchableError, "cond-> requires at least 1 argument") + let expr = args[0] + let gs = cljSymbol(gensymName("ct_")) + var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])] + var i = 1 + while i + 1 < args.len: + let test = args[i] + let step = args[i+1] + var threaded: CljVal + if step.kind == ckList and step.items.len > 0: + var newItems = step.items + newItems.insert(gs, 1) + threaded = cljList(newItems) + elif step.kind == ckSymbol: + threaded = cljList(@[step, gs]) + else: + threaded = step + forms.add(cljList(@[cljSymbol("if"), test, + cljList(@[cljSymbol("set!"), gs, threaded]), + gs])) + i += 2 + forms.add(gs) + cljList(forms) + ) + + # cond->> + defineMacro("cond->>", proc(args: seq[CljVal]): CljVal = + if args.len < 1: + raise newException(CatchableError, "cond->> requires at least 1 argument") + let expr = args[0] + let gs = cljSymbol(gensymName("ctg_")) + var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, expr])] + var i = 1 + while i + 1 < args.len: + let test = args[i] + let step = args[i+1] + var threaded: CljVal + if step.kind == ckList and step.items.len > 0: + var newItems = step.items + newItems.add(gs) + threaded = cljList(newItems) + elif step.kind == ckSymbol: + threaded = cljList(@[step, gs]) + else: + threaded = step + forms.add(cljList(@[cljSymbol("if"), test, + cljList(@[cljSymbol("set!"), gs, threaded]), + gs])) + i += 2 + forms.add(gs) + cljList(forms) + ) + + # doto + defineMacro("doto", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "doto requires at least 2 arguments") + let x = args[0] + let gs = cljSymbol(gensymName("do_")) + var forms: seq[CljVal] = @[cljSymbol("let"), cljVector(@[gs, x])] + for i in 1.. 0: + var newItems = step.items + newItems.insert(gs, 1) + forms.add(cljList(newItems)) + elif step.kind == ckSymbol: + forms.add(cljList(@[step, gs])) + forms.add(gs) + cljList(forms) + ) + + # as-> + defineMacro("as->", proc(args: seq[CljVal]): CljVal = + if args.len < 3: + raise newException(CatchableError, "as-> requires at least 3 arguments") + let expr = args[0] + let name = args[1] + var result = expr + for i in 2.. + defineMacro("some->", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "some-> requires at least 2 arguments") + var result = args[0] + for i in 1.. 0: + var newItems = step.items + newItems.insert(gs, 1) + threaded = cljList(newItems) + elif step.kind == ckSymbol: + threaded = cljList(@[step, gs]) + else: + threaded = step + result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), + cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]), + cljNil(), threaded])]) + result + ) + + # some->> + defineMacro("some->>", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "some->> requires at least 2 arguments") + var result = args[0] + for i in 1.. 0: + var newItems = step.items + newItems.add(gs) + threaded = cljList(newItems) + elif step.kind == ckSymbol: + threaded = cljList(@[step, gs]) + else: + threaded = step + result = cljList(@[cljSymbol("let"), cljVector(@[gs, result]), + cljList(@[cljSymbol("if"), cljList(@[cljSymbol("nil?"), gs]), + cljNil(), threaded])]) + result + ) + + # for (simplified — single binding only) + defineMacro("for", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "for requires binding vector and body") + let bindings = args[0] + let body = args[1..^1] + if bindings.kind != ckVector or bindings.items.len < 2: + raise newException(CatchableError, "for requires a binding vector [name coll]") + let sym = bindings.items[0] + let coll = bindings.items[1] + let bodyExpr = if body.len == 1: body[0] else: cljList(@[cljSymbol("do")] & body) + let gs = cljSymbol(gensymName("f_")) + cljList(@[cljSymbol("let"), cljVector(@[gs, cljVector(@[])]), + cljList(@[cljSymbol("doseq"), cljVector(@[sym, coll]), + cljList(@[cljSymbol("set!"), gs, + cljList(@[cljSymbol("conj"), gs, bodyExpr])])]), + gs]) + ) + + # doseq (simplified — single binding only) + defineMacro("doseq", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "doseq requires binding vector and body") + let bindings = args[0] + let body = args[1..^1] + if bindings.kind != ckVector or bindings.items.len < 2: + raise newException(CatchableError, "doseq requires a binding vector [name coll]") + let sym = bindings.items[0] + let coll = bindings.items[1] + let gs = cljSymbol(gensymName("ds_")) + let bodyExpr = if body.len == 1: body[0] else: cljList(@[cljSymbol("do")] & body) + cljList(@[cljSymbol("let"), cljVector(@[gs, coll]), + cljList(@[cljSymbol("loop"), cljVector(@[cljSymbol(gensymName("i_")), cljInt(0)]), + cljList(@[cljSymbol("when"), + cljList(@[cljSymbol("<"), cljSymbol(gensymName("i_")), cljList(@[cljSymbol("count"), gs])]), + cljList(@[cljSymbol("let"), cljVector(@[sym, cljList(@[cljSymbol("nth"), gs, cljSymbol(gensymName("i_"))])]), + bodyExpr]), + cljList(@[cljSymbol("recur"), + cljList(@[cljSymbol("inc"), cljSymbol(gensymName("i_"))])])])])]) + ) + + # dotimes + defineMacro("dotimes", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "dotimes requires binding vector and body") + let bindings = args[0] + let body = args[1..^1] + if bindings.kind != ckVector or bindings.items.len != 2: + raise newException(CatchableError, "dotimes requires [name n]") + let sym = bindings.items[0] + let n = bindings.items[1] + cljList(@[cljSymbol("doseq"), cljVector(@[sym, cljList(@[cljSymbol("range"), n])])] & body) + ) + + # defmacro + defineMacro("defmacro", proc(args: seq[CljVal]): CljVal = + # defmacro is handled specially in the compiler, this is a no-op + cljNil() + ) + + # comment + defineMacro("comment", proc(args: seq[CljVal]): CljVal = + cljNil() + ) + + # assert + defineMacro("assert", proc(args: seq[CljVal]): CljVal = + if args.len == 0: return cljNil() + let test = args[0] + let msg = if args.len > 1: args[1] else: cljString("Assert failed") + cljList(@[cljSymbol("when-not"), test, + cljList(@[cljSymbol("throw"), msg])]) + ) + + # with-open + defineMacro("with-open", proc(args: seq[CljVal]): CljVal = + if args.len < 2: + raise newException(CatchableError, "with-open requires binding and body") + let binding = args[0] + let body = args[1..^1] + if binding.kind != ckVector or binding.items.len != 2: + raise newException(CatchableError, "with-open requires [name init]") + let sym = binding.items[0] + let init = binding.items[1] + let gs = cljSymbol(gensymName("wo_")) + cljList(@[cljSymbol("let"), cljVector(@[gs, init]), + cljList(@[cljSymbol("try")] & body & @[ + cljList(@[cljSymbol("finally"), + cljList(@[cljSymbol(".close"), gs])])])]) + ) diff --git a/src/reader.nim b/src/reader.nim index bb8096f..2e26d7e 100644 --- a/src/reader.nim +++ b/src/reader.nim @@ -1,5 +1,5 @@ # Clojure Reader (EDN subset) -import strutils, sequtils +import strutils import types type @@ -17,7 +17,7 @@ proc skipWhitespaceAndComments(s: string, i: var int) = break proc isSymChar(c: char): bool = - c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&'} + c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&', ':'} proc readStringTok(s: string, i: var int): string = inc i # skip opening quote @@ -126,7 +126,9 @@ proc readList(s: string, i: var int): CljVal = if s[i] == ')': inc i break - items.add(readForm(s, i)) + let form = readForm(s, i) + if form != nil: + items.add(form) return cljList(items) proc readVector(s: string, i: var int): CljVal = @@ -139,9 +141,97 @@ proc readVector(s: string, i: var int): CljVal = if s[i] == ']': inc i break - items.add(readForm(s, i)) + let form = readForm(s, i) + if form != nil: + items.add(form) return cljVector(items) +proc readMap(s: string, i: var int): CljVal = + inc i # skip '{' + var pairs: seq[(CljVal, CljVal)] = @[] + while true: + skipWhitespaceAndComments(s, i) + if i >= s.len: + raise newException(ReaderError, "Unterminated map") + if s[i] == '}': + inc i + break + let key = readForm(s, i) + if key == nil: + continue + skipWhitespaceAndComments(s, i) + if i >= s.len or s[i] == '}': + raise newException(ReaderError, "Map must have even number of forms") + let val = readForm(s, i) + if val == nil: + raise newException(ReaderError, "Map value cannot be nil") + pairs.add((key, val)) + return cljMapFromPairs(pairs) + +proc readSet(s: string, i: var int): CljVal = + inc i # skip '{' after # + var items: seq[CljVal] = @[] + while true: + skipWhitespaceAndComments(s, i) + if i >= s.len: + raise newException(ReaderError, "Unterminated set") + if s[i] == '}': + inc i + break + let form = readForm(s, i) + if form != nil: + items.add(form) + return cljList(@[cljSymbol("set"), cljVector(items)]) + +proc readDispatch(s: string, i: var int): CljVal = + inc i # skip '#' + if i >= s.len: + raise newException(ReaderError, "Unexpected end after #") + let c = s[i] + case c + of '{': + return readSet(s, i) + of '(': + let fnBody = readList(s, i) + return cljList(@[cljSymbol("fn*"), cljVector(@[cljSymbol("%")]), fnBody]) + of '\'': + inc i + let sym = readForm(s, i) + return cljList(@[cljSymbol("var"), sym]) + of '_': + inc i + discard readForm(s, i) + return nil + else: + raise newException(ReaderError, "Unknown dispatch macro: #" & c) + +proc readWithMeta(s: string, i: var int): CljVal = + inc i # skip '^' + let meta = readForm(s, i) + let form = readForm(s, i) + return cljList(@[cljSymbol("with-meta"), form, meta]) + +proc readSyntaxQuote(s: string, i: var int): CljVal = + inc i # skip '`' + let form = readForm(s, i) + return cljList(@[cljSymbol("syntax-quote"), form]) + +proc readUnquoteSplicing(s: string, i: var int): CljVal = + inc i # skip '~' + inc i # skip '@' + let form = readForm(s, i) + return cljList(@[cljSymbol("unquote-splicing"), form]) + +proc readUnquote(s: string, i: var int): CljVal = + inc i # skip '~' + let form = readForm(s, i) + return cljList(@[cljSymbol("unquote"), form]) + +proc readDeref(s: string, i: var int): CljVal = + inc i # skip '@' + let form = readForm(s, i) + return cljList(@[cljSymbol("deref"), form]) + proc readForm(s: string, i: var int): CljVal = skipWhitespaceAndComments(s, i) if i >= s.len: @@ -153,12 +243,26 @@ proc readForm(s: string, i: var int): CljVal = return readList(s, i) of '[': return readVector(s, i) + of '{': + return readMap(s, i) of '"': return cljString(readStringTok(s, i)) of '\'': inc i # skip quote let form = readForm(s, i) return cljList(@[cljSymbol("quote"), form]) + of '#': + return readDispatch(s, i) + of '^': + return readWithMeta(s, i) + of '`': + return readSyntaxQuote(s, i) + of '~': + if i + 1 < s.len and s[i+1] == '@': + return readUnquoteSplicing(s, i) + return readUnquote(s, i) + of '@': + return readDeref(s, i) else: return readAtom(s, i) @@ -166,15 +270,25 @@ proc readOne*(s: string, i: var int): CljVal = skipWhitespaceAndComments(s, i) if i >= s.len: return nil - return readForm(s, i) + result = readForm(s, i) + while result == nil: + skipWhitespaceAndComments(s, i) + if i >= s.len: + return nil + result = readForm(s, i) proc read*(s: string): CljVal = var i = 0 - let result = readForm(s, i) + var res: CljVal = nil + while res == nil: + skipWhitespaceAndComments(s, i) + if i >= s.len: + raise newException(ReaderError, "No form found") + res = readForm(s, i) skipWhitespaceAndComments(s, i) if i < s.len: raise newException(ReaderError, "Extra input after form: " & s[i..^1]) - return result + return res proc readAll*(s: string): seq[CljVal] = var i = 0 @@ -183,5 +297,7 @@ proc readAll*(s: string): seq[CljVal] = skipWhitespaceAndComments(s, i) if i >= s.len: break - forms.add(readForm(s, i)) + let form = readForm(s, i) + if form != nil: + forms.add(form) return forms diff --git a/src/runtime.nim b/src/runtime.nim new file mode 100644 index 0000000..e1840bf --- /dev/null +++ b/src/runtime.nim @@ -0,0 +1,253 @@ +import hashes + +const + Bits = 5 + Width = 1 shl Bits # 32 + Mask = Width - 1 + +type + CljKind* = enum + ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol, + ckList, ckVector, ckMap, ckFn, ckAtom + + CljVal* = ref CljValObj + CljValObj = 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: listItems*: seq[CljVal] + of ckVector: vecRoot*: VecNode + of ckMap: mapRoot*: MapNode + of ckFn: fnProc*: proc(args: seq[CljVal]): CljVal + of ckAtom: atomVal*: CljVal + + VecNodeKind = enum vnLeaf, vnInternal + VecNode = ref object + case kind: VecNodeKind + of vnLeaf: + leaf: array[Width, CljVal] + of vnInternal: + children: array[Width, VecNode] + + PersistentVector* = object + count*: int + shift*: int + root*: VecNode + tail*: seq[CljVal] + tailLen*: int + + MapNodeKind = enum mnLeaf, mnInternal, mnCollision + MapNode = ref object + case kind: MapNodeKind + of mnLeaf: + leafKey*: CljVal + leafVal*: CljVal + of mnInternal: + bitmap*: uint32 + children*: seq[MapNode] + of mnCollision: + collHash*: Hash + collPairs*: seq[(CljVal, CljVal)] + + PersistentMap* = object + count*: int + root*: MapNode + hasLeaf*: bool + rootLeaf*: MapNode + +# ---- 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 cljInt*(v: int): CljVal = CljVal(kind: ckInt, intVal: v.int64) +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 cljFn*(p: proc(args: seq[CljVal]): CljVal): CljVal = CljVal(kind: ckFn, fnProc: p) + +proc emptyVecNode(): VecNode = VecNode(kind: vnLeaf) +proc emptyMapNode(): MapNode = MapNode(kind: mnInternal, bitmap: 0, children: @[]) + +# ---- CljVal helpers ---- + +proc cljValHash*(v: CljVal): Hash = + case v.kind + of ckNil: hash(0) + of ckBool: hash(v.boolVal) + of ckInt: hash(v.intVal) + of ckFloat: hash(v.floatVal) + of ckString: hash(v.strVal) + of ckKeyword: hash(v.kwName) !& hash(":kw") + of ckSymbol: hash(v.symName) + else: hash(cast[uint](v)) + +proc `==`*(a, b: CljVal): bool = + if a.isNil and b.isNil: return true + if a.isNil or b.isNil: return false + if a.kind != b.kind: return false + case a.kind + of ckNil: true + of ckBool: a.boolVal == b.boolVal + of ckInt: a.intVal == b.intVal + of ckFloat: a.floatVal == b.floatVal + of ckString: a.strVal == b.strVal + of ckKeyword: a.kwName == b.kwName + of ckSymbol: a.symName == b.symName + else: false + +# ---- Persistent Vector (HAMT) ---- + +proc newPersistentVector*(): PersistentVector = + PersistentVector(count: 0, shift: Bits, root: emptyVecNode(), tail: @[], tailLen: 0) + +proc tailOff(count: int): int = + if count < Width: 0 + else: ((count - 1) shr Bits) shl Bits + +proc vecPush(v: PersistentVector, val: CljVal): PersistentVector = + if v.count - tailOff(v.count) < Width: + # room in tail + var newTail = v.tail + newTail.add(val) + return PersistentVector(count: v.count + 1, shift: v.shift, root: v.root, tail: newTail, tailLen: v.tailLen + 1) + # tail full, push into tree + let tailNode = VecNode(kind: vnLeaf, leaf: block: + var arr: array[Width, CljVal] + for i in 0.. (1 shl v.shift): + var newRootN = VecNode(kind: vnInternal) + newRootN.children[0] = v.root + newRootN.children[1] = tailNode + newRoot = newRootN + newShift += Bits + else: + # insert tail into tree + proc insertTail(node: VecNode, level: int, parentBitmap: uint32): VecNode = + discard + # simplified: just create leaf for now + newRoot = v.root + var newTailSeq = @[val] + return PersistentVector(count: v.count + 1, shift: newShift, root: newRoot, tail: newTailSeq, tailLen: 1) + +proc vecGet(v: PersistentVector, idx: int): CljVal = + if idx < 0 or idx >= v.count: + raise newException(IndexDefect, "Index out of range: " & $idx) + let to = tailOff(v.count) + if idx >= to: + return v.tail[idx - to] + var node = v.root + var level = v.shift + while level > 0: + node = node.children[(idx shr level) and Mask] + level -= Bits + return node.leaf[idx and Mask] + +# ---- Simplified seq-based persistent vector ---- +# For now use Nim seq with copy-on-write semantics via wrapper + +type + CljVec* = ref object + data*: seq[CljVal] + + CljMap* = ref object + keys*: seq[CljVal] + vals*: seq[CljVal] + +proc newCljVec*(): CljVec = CljVec(data: @[]) +proc newCljVec*(items: seq[CljVal]): CljVec = CljVec(data: items) + +proc cljVecLen*(v: CljVec): int = v.data.len +proc cljVecGet*(v: CljVec, i: int): CljVal = + if i < 0 or i >= v.data.len: + raise newException(IndexDefect, "Index out of range: " & $i) + v.data[i] + +proc cljVecConj*(v: CljVec, val: CljVal): CljVec = + var newData = v.data + newData.add(val) + newCljVec(newData) + +proc cljVecAssoc*(v: CljVec, i: int, val: CljVal): CljVec = + var newData = v.data + if i == newData.len: + newData.add(val) + elif i >= 0 and i < newData.len: + newData[i] = val + else: + raise newException(IndexDefect, "Index out of range: " & $i) + newCljVec(newData) + +proc newCljMap*(): CljMap = CljMap(keys: @[], vals: @[]) + +proc cljMapGet*(m: CljMap, key: CljVal): CljVal = + for i in 0.." + of ckAtom: "(atom " & cljRepr(v.atomVal) & ")" + +proc cljStr*(v: CljVal): string = + if v.isNil: return "" + case v.kind + of ckString: v.strVal + of ckKeyword: ":" & v.kwName + of ckSymbol: v.symName + else: cljRepr(v) diff --git a/src/types.nim b/src/types.nim index c60df3c..b5ee960 100644 --- a/src/types.nim +++ b/src/types.nim @@ -1,4 +1,4 @@ -# Clojure Value Types (Nim Runtime) +# Clojure Value Types (Compiler internal representation) import sequtils, strutils type @@ -16,9 +16,10 @@ type of ckKeyword: kwName*: string of ckSymbol: symName*: string of ckList, ckVector: items*: seq[CljVal] - of ckMap: pairs*: seq[(CljVal, CljVal)] + of ckMap: + mapKeys*: seq[CljVal] + mapVals*: seq[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) @@ -28,10 +29,20 @@ 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 cljMap*(keys: seq[CljVal], vals: seq[CljVal]): CljVal = + CljVal(kind: ckMap, mapKeys: keys, mapVals: vals) + +proc cljMapFromPairs*(pairs: seq[(CljVal, CljVal)]): CljVal = + var ks: seq[CljVal] = @[] + var vs: seq[CljVal] = @[] + for (k, v) in pairs: + ks.add(k) + vs.add(v) + cljMap(ks, vs) + proc `$`*(v: CljVal): string = + if v.isNil: return "nil" case v.kind of ckNil: "nil" of ckBool: $v.boolVal @@ -44,6 +55,6 @@ proc `$`*(v: CljVal): string = of ckVector: "[" & v.items.mapIt($it).join(" ") & "]" of ckMap: var parts: seq[string] = @[] - for (k, v2) in v.pairs: - parts.add($k & " " & $v2) + for i in 0.. x 2)) [1 2 3])") + let code = emitExpr(v) + check "cljFilter" in code + check "cljFn" in code + + test "emit reduce": + let v = read("(reduce + 0 [1 2 3])") + let code = emitExpr(v) + check "cljReduce" in code diff --git a/tests/test_reader.nim b/tests/test_reader.nim new file mode 100644 index 0000000..3da2ef6 --- /dev/null +++ b/tests/test_reader.nim @@ -0,0 +1,160 @@ +import unittest +import ../src/types +import ../src/reader + +suite "Reader - Basic Types": + test "read nil": + let v = read("nil") + check v.kind == ckNil + + test "read true": + let v = read("true") + check v.kind == ckBool + check v.boolVal == true + + test "read false": + let v = read("false") + check v.kind == ckBool + check v.boolVal == false + + test "read integer": + let v = read("42") + check v.kind == ckInt + check v.intVal == 42 + + test "read negative integer": + let v = read("-7") + check v.kind == ckInt + check v.intVal == -7 + + test "read float": + let v = read("3.14") + check v.kind == ckFloat + check v.floatVal == 3.14 + + test "read string": + let v = read("\"hello\"") + check v.kind == ckString + check v.strVal == "hello" + + test "read string with escapes": + let v = read("\"hello\\nworld\"") + check v.kind == ckString + check v.strVal == "hello\nworld" + + test "read keyword": + let v = read(":key") + check v.kind == ckKeyword + check v.kwName == "key" + + test "read symbol": + let v = read("foo") + check v.kind == ckSymbol + check v.symName == "foo" + + test "read symbol with special chars": + let v = read("my-fn?") + check v.kind == ckSymbol + check v.symName == "my-fn?" + +suite "Reader - Collections": + test "read empty list": + let v = read("()") + check v.kind == ckList + check v.items.len == 0 + + test "read list": + let v = read("(+ 1 2)") + check v.kind == ckList + check v.items.len == 3 + check v.items[0].kind == ckSymbol + check v.items[0].symName == "+" + check v.items[1].kind == ckInt + check v.items[1].intVal == 1 + check v.items[2].kind == ckInt + check v.items[2].intVal == 2 + + test "read vector": + let v = read("[1 2 3]") + check v.kind == ckVector + check v.items.len == 3 + check v.items[0].intVal == 1 + check v.items[1].intVal == 2 + check v.items[2].intVal == 3 + + test "read empty vector": + let v = read("[]") + check v.kind == ckVector + check v.items.len == 0 + + test "read map": + let v = read("{:a 1 :b 2}") + check v.kind == ckMap + check v.mapKeys.len == 2 + check v.mapKeys[0].kind == ckKeyword + check v.mapKeys[0].kwName == "a" + check v.mapVals[0].kind == ckInt + check v.mapVals[0].intVal == 1 + + test "read empty map": + let v = read("{}") + check v.kind == ckMap + check v.mapKeys.len == 0 + + test "read nested list": + let v = read("(+ (- 3 1) 2)") + check v.kind == ckList + check v.items.len == 3 + check v.items[1].kind == ckList + check v.items[1].items.len == 3 + +suite "Reader - Quote and Syntax": + test "read quote": + let v = read("'foo") + check v.kind == ckList + check v.items.len == 2 + check v.items[0].kind == ckSymbol + check v.items[0].symName == "quote" + check v.items[1].kind == ckSymbol + check v.items[1].symName == "foo" + + test "read deref": + let v = read("@atom") + check v.kind == ckList + check v.items[0].symName == "deref" + + test "read unquote": + let v = read("~x") + check v.kind == ckList + check v.items[0].symName == "unquote" + + test "read unquote-splicing": + let v = read("~@x") + check v.kind == ckList + check v.items[0].symName == "unquote-splicing" + +suite "Reader - Comments": + test "skip comments": + let v = read("; comment\n42") + check v.kind == ckInt + check v.intVal == 42 + + test "inline comments": + let forms = readAll("(+ 1 2) ; addition\n(- 3 1)") + check forms.len == 2 + +suite "Reader - readAll": + test "read multiple forms": + let forms = readAll("1 2 3") + check forms.len == 3 + check forms[0].intVal == 1 + check forms[1].intVal == 2 + check forms[2].intVal == 3 + + test "read empty string": + let forms = readAll("") + check forms.len == 0 + + test "read whitespace only": + let forms = readAll(" \n \t ") + check forms.len == 0