From b190367681fe8b6abf4806906fabed7f0e7f18ce Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:07:13 +0200 Subject: [PATCH] add thesis text --- doc/text/.gitignore | 9 + doc/text/README.md | 1 + doc/text/build | 45 + doc/text/document.md | 1252 ++++++++++++++++++++++++++++ doc/text/img/debugging-example.png | Bin 0 -> 98441 bytes doc/text/img/dependencies.svg | 4 + doc/text/img/stack-arr.png | Bin 0 -> 37167 bytes doc/text/requirements.md | 14 + doc/text/src/build.lua | 728 ++++++++++++++++ doc/text/src/json.lua | 244 ++++++ doc/text/src/perf.lua | 30 + doc/text/src/utils.lua | 429 ++++++++++ doc/text/template.html | 555 ++++++++++++ 13 files changed, 3311 insertions(+) create mode 100644 doc/text/.gitignore create mode 100644 doc/text/README.md create mode 100755 doc/text/build create mode 100644 doc/text/document.md create mode 100644 doc/text/img/debugging-example.png create mode 100644 doc/text/img/dependencies.svg create mode 100644 doc/text/img/stack-arr.png create mode 100644 doc/text/requirements.md create mode 100644 doc/text/src/build.lua create mode 100644 doc/text/src/json.lua create mode 100644 doc/text/src/perf.lua create mode 100644 doc/text/src/utils.lua create mode 100644 doc/text/template.html diff --git a/doc/text/.gitignore b/doc/text/.gitignore new file mode 100644 index 0000000..fb06c69 --- /dev/null +++ b/doc/text/.gitignore @@ -0,0 +1,9 @@ +/* +!/img +!/src +!/.gitignore +!/build +!/document.md +!/README.md +!/requirements.md +!/template.html diff --git a/doc/text/README.md b/doc/text/README.md new file mode 100644 index 0000000..34175cf --- /dev/null +++ b/doc/text/README.md @@ -0,0 +1 @@ +This is the main body of my thesis. **BE WARNED!** It is quite lengthy and written in Bulgarian. diff --git a/doc/text/build b/doc/text/build new file mode 100755 index 0000000..4b607c7 --- /dev/null +++ b/doc/text/build @@ -0,0 +1,45 @@ +#!/bin/luajit -lsrc.build + +local config = require "config"; + +function profession(val) + if val == "sys" then + return { + profession = "481020 „Системен програмист“", + specialty = "4810201 „Системно програмиране“", + }; + elseif val == "net" then + return combine { + profession = "523050 „Техник на компютърни системи“", + specialty = "5230502 „Компютърни мрежи“", + }; + end +end + +emit { + target = "res.html", + template { + template = "template.html", + + build { + "requirements.md", + content = "requirements", + }, + build { + "document.md", + content = "content", + toc = "toc", + ctx = { + chapter_format = "Глава %s
", + chapter_format_plain = "Глава %s: ", + }, + }, + + profession(config.profession), + + year = os.date "%Y", + prev_year = os.date "%Y" - 1, + + config, + }, +} diff --git a/doc/text/document.md b/doc/text/document.md new file mode 100644 index 0000000..d2afe65 --- /dev/null +++ b/doc/text/document.md @@ -0,0 +1,1252 @@ +--- +title: Дипломна работа +--- +# Увод {.nonum} + +Един от най-важните принципи на програмирането е този на разширимостта. Този принцип повелява, че софтуерът трябва да изграждан по начин, по който може неговата функционалност да бъде разширена без вече написаният софтуер да бъде променян. Това е добре известен проблем в сферата на софтуерното инженерство, като едно от най-ефективните решения е интегрирането на втори помощен език, чрез който потребителя да може да разширява функционалността на оригиналният продукт. + +Но защо разширимостта е толкова важна? Това се дължи на факта, че често потребителите на софтуерните продукти искат да разширят функционалността на продукта. Традиционно, разширяването може да стане само като потребителя се свърже с разработчика на продукта и да помоли за добавянето на функционалността. Това обаче е не само непрактично, но и води до прекомерно нарастване на софтуера, което го прави сложен за поддръжка и използване. + +Въвеждането на скриптови езици като помощни механизми за разширяване на функционалността предлага значително по-ефективно решение. Езиците като Python, Lua и JavaScript могат да бъдат използвани за добавяне на нова функционалност, без да се променя основният код на приложението, като по този начин се осигурява гъвкавост на софтуерния продукт. JavaScript, в частност, е един от най-широко използваните езици в съвременната разработка, поради своята популярност, която се дължи на употребата на езика в уеб технологиите. + +Целта на тази дипломна работа е да създаде лесно вградим, малък на размер интерпретатор на „EcmaScript 5.1“ езика (по-добре познат като JavaScript, както ще се и нарича оттук нататък) за продукти, написани на езикът „Java“. + +# Проучвателна част + +## Съществуващи JavaScript интерпретатори + +### V8 + +V8 [@refs-v8] е интерпретатора за JavaScript на Google, написан на C++. Той е най-широко разпространената среда за изпълнение на JavaScript код. Създаден е, за да служи на Chromium браузърът (на който е базиран Google Chrome, който има близо 70% пазарен дял [@refs-market-share]). Освен това V8 се използва и от много среди за разработка на сървърен код, най-популярните от които са „NodeJS“ [@refs-nodejs] и „Deno“ [@refs-deno]. Поради своята популярност, имплементацията на JavaScript от V8 е „де факто“ стандарта за езика, заради което по времето на разработката на този проекто, NodeJS, базиран на V8, беше използван за сферяване на резултатите от този проект. Архитектурно, V8 използва „JIT“ (Just-in-Time) технологията за компилиране, която обаче не е засегната тук. + +- **Предимства** + - Бързодействие + - Добра поддръжка от Google + - Популярен, следователно добра поддръжка от обществото +- **Недостатъци:** + - Голям размер на крайният изпълним код + - Сложно вграждане в проекти, които не са написани на C++ + +### JavaScriptCore + +JavaScriptCore [@refs-jsc] е интерпретатора за JavaScript на Apple, написан на C++. Не е толкова разпространен, колкото V8, но въпреки това намира употреба в браузъра на Apple „Safari“, както и алтернативната на NodeJS среда за изпълнение „Bun“. Въпреки че не е толкова разпространен, JavaScriptCore е компетентен интерпретатор, като по много параметри се доближава и даже превъзхожда V8. Интерпретатора, подобно на V8, използва „Just-in-Time“ технологията за компилиране. + +- **Предимства:** + - Бързодействие + - Добра поддръжка от Apple +- **Недостатъци:** + - Голям размер на крайният изпълним код + - Сложно вграждане в проекти, които не са написани на C++ + +---- + +### Nashorn + +Rhino [@refs-rhino], създаден през 1997 г. от Netscape като част от тогавашните им усилията да пренапишат „Navigator“ на Java, е един от първите интерпретатори за JavaScript, написани на Java [@refs-rhino-history]. Първоначално интерпретатора е компилирал JavaScript кода към Java байт-код, но поради редица проблеми е въведен нов режим на интерпретирано изпълнение. След като усилията на Netscape да пренапишат браузъра си на Java приключват, Rhino остава като JavaScript интерпретатор, който се използва от много проекти. За голям период от време, Rhino остава де факто стандартния JavaScript интерпретатор за Java. + +- **Предимства** + - Малък размер + - Лесно вградим в проекти +- **Недостатъци:** + - Не се поддържа толкова активно + - Лоша производителност + - Непълно покритие на EcmaScript 5.1 + +---- + +### Rhino + +Nashorn [@refs-nashorn] е първият официален JavaScript интерпретатор на Oracle, вграден във виртуалната машина на Java от версия 8. Той е по-бърз от неговият предходник Rhino, понеже Nashorn отново компилира JavaScript кода към Java байт-код. От 15та версия на Java виртуалната версия включително, Oracle премахва Nashorn от виртуалната машина, като проекта бива прехвърлен на Mozilla. + +- **Предимства** + - Малък размер + - Лесно вградим в проекти + - Пълно покритие на EcmaScript 5.1 + - Задоволително бързодействие +- **Недостатъци:** + - Не се поддържа толкова активно + +### GraalJS + +GraalJS [@refs-graaljs] е най-новият JavaScript интерпретатор на Oracle, написан на Java. Той е базиран на Truffle [@refs-truffle], среда за „полиглотно“ изпълнение на код - прави възможно изпълнението на няколко езика в една среда едновременно. Освен че GraalJS използва средата Truffle, която компилира кода до ефективен Java байт-код, ако се използва алтернативната виртуална машина на Oracle GraalVM, може да бъде постигната производителност, съпоставима с V8. Въпреки че производителността, която GraalJS демонстрира е впечатляваща, проекта е с изключително голям размер, което означава, че ако даден проект има GraalJS като зависимост, размерите на изпълнимите файлове на проекта ще се умножат неколкократно. + +- **Предимства** + - Бързодействие + - Добра поддръжка от Oracle +- **Недостатъци:** + - Голям размер на крайният изпълним код + - Сложен и негъвкав публичен интерфейс + +### QuickJS + +QuickJS [@refs-quickjs] е интерпретатор на JavaScript. Той споделя основната философията на този проект - да може да се вгражда лесно в други проекти, както и да бъде малък на размер. По производителност интерпретатора е около 2 - 3 пъти по-бавен от V8 в режим без компилация [@refs-quickjs-benchmark]. Проекта се разработва и поддържа от един човек - Фабрис Белърд, който освен този проект, поддържа и други проекти като FFMpeg, QEMU и TCC. + +- **Предимства** + - Малък размер + - Задоволително бързодействие + - Лесно вграждане в проекти +- **Недостатъци:** + - Сложно вграждане в проекти, които не са написани на C/C++ + +---- + +## Стандарти и документация за JavaScript + +### Стандарт ECMA 262 (версия 5.1) + +Това е стандартът [@refs-ecma-262], който документира езикът JavaScript. Въпреки че има по-нови версии на този стандарт, този проект имплементира само тази версия на стандарта (по-нататък ще бъде уточнено защо). Стандарта се съблюдава и развива от TC39 комитета на ECMA [@refs-tc39]. Стандартът съдържа информация за всички свойства на JavaScript: + +- Синтаксис +- Семантики +- Стандартни библиотеки +- DOM библиотеки +- Допълнения, които уточняват поддръжката на по-старите версии на стандарта + +Този стандарт е основата, върху която този проект е създаден, понеже е източника на истина, който всеки интерпретатор трябва да спазва. + +### MDN Уеб документация + +MDN Уеб документацията [@refs-mdn] е документация, създадена от общността, с подобна на Уикипедия философия - всеки може да прави приноси към документацията. Тази документация е един от най-ценните и широко използвани източници на информация както за JavaScript, така и за всичко останало, свързано с уеб технологиите. Въпреки че тази документация не е създадена от ECMA, тя напълно задоволително обобщава ECMA 262 стандарта, заради което този ресурс беше използван често като заместител на ECMA 262 стандарта. + +## Методи за изпълнение на код + +За да се изпълни код е нужно той първо да бъде разчетен и превърнат във форма, четима за интерпретатора. В тази дипломна работа обаче не е засегнато разчитането на код (може да видите повече за приложения примерен компилатор в [@допълнение]). + +### Директно разчитане на код + +При този метод на изпълнение на код интерпретатора директно разчита и изпълнява описаните от кода действия. Това е най-неефективният метод за изпълнение на код, понеже всеки път когато дадена операция се изпълнява се налага тя да бъде разчетена наново, което може да бъде скъпа операция. Този метод се използва рядко в практиката, най-вече за „Shell“ езици и някой по-стари интерпретирани езици като „Basic“. + +### Компилиране до изпълним файл + +При този метод се създава програма, наречена „компилатор“ [@refs-compilers], която разчита кода и го превежда към изпълним за средата файл. Едва тогава изпълнимият файл може да бъде изпълнен, като той ще извърши същите действия и алгоритми, които са описани във входния код. Този метод традиционно се ползва за системни езици, но има проекти, които прилагат този метод и за традиционно скриптови езици. + +### Хибриден - компилиране до междинен език + +При тази архитектура се създава междинен език [@refs-il], който е по-лесен за разчитане. Не е задължително този език да има текстово представяне и най-често е асемблероподобен. След това се създават два основни компонента - компилатора, който превръща кода към този междинен език и интерпретатора, който разчита и изпълнява междинния език. Повечето модерни интерпретатори използват тази архитектура, като някой езици отделят компилатора като отделна програма, която разработчика трябва да изпълни, за да създаде изпълним код за „виртуалната машина“ на междинният език. На този принцип работят езиците „Java“ (компилиран до Java байт-код) и „C#“ (компилиран до CIL). + +:::{.figure} + +```cs +.assembly Hello {} +.assembly extern mscorlib {} +.method static void Main() +{ + .entrypoint + .maxstack 1 + ldstr "Hello, world!" + call void [mscorlib]System.Console::WriteLine(string) + ret +} +``` + +Примерен CIL код, компилиран от C# +::: + +### Компилация в реално време - JiT + +Компилацията в реално време [@refs-jit] е разширение на хибридните интерпретатори, при които междинният език, когато интерпретатора прецени, се превежда към изпълним за процесора код. При компилацията на междинния език могат да бъдат използвани редица методи за оптимизация, като: + +- Оптимизиране на дадени пътища на изпълнение за сметка на други +- Правене на предположения за стойностите, с които кода ще работи +- Статистически анализ на пътищата на изпълнение и стойностите, с които кода работи +- Размотаване на циклите (loop unrolling) +- Вграждане на функция (function inlining) + +Някои по-модерни интерпретатори (като V8 и OpenJDK) използват компилация в реално време, обаче този проект ще използва само хибридния модел за интерпретиране. + +# Архитектура и избор на развойна среда + +## Концепция на интерпретатора на JavaScript + +Основната концепция на този проект е да направи възможно изпълнението на JavaScript код под Java, като особена важност се придава на възможността за лесно вграждане на интерпретатора в съществуващ проект. За целта, следните основни изисквания трябва да бъдат покрити от интерпретатора, подредени по важност от най-важни до по-маловажни: + +- Пълно покритие на семантиките на Ecma 262 5.1 стандарта +- Удобен API за употреба от програмисти +- Да няма зависимости от други библиотеки (за по-лесно вграждане и по-малък размер на компилираната библиотеката) +- Възможност за лесно вграждане в съществуващ Java проект +- Възможност за лесна съвместна работа на Java и JavaScript +- Достатъчно добра производителност (не е фокус на проекта, но все пак е добра идея проектът да има добра производителност) +- Добра изолация на JavaScript кода от Java кода (за сигурност) +- Поддръжка на по-старите версии на Java (за да може да бъде използван проекта навсякъде) + +## Избор на инструменти за разработка на проекта + +### Език за разработка - Java + +Java [@refs-java] е обектно-ориентиран език от високо ниво с автоматично управление на паметта (чрез т. нар. „Garbage Collector“). Езикът се компилира към байт-код и има типова проверка по време на компилацията. Компилираният байт-код не е изпълним от процесор, а се изпълнява чрез виртуалната машина на Java (JVM), което позволява на всеки софтуер, написан на Java да може да бъде изпълнен на всяка машина, за която съществува Java виртуална машина. Силната типизация на Java пък прави някои типове грешки по-сложни. + +Езикът има обширна стандартна библиотека и богат набор от разработени от обществото библиотеки. За този проект обаче няма да бъдат използвани тези библиотеки + +::: {.figure} + +```java +public class Program { + public static boolean isSorted(int[] arr) { + if (arr.length < 2) return true; + + int last = arr[0]; + + for (int i = 1; i < arr.length; i++) { + int curr = arr[i]; + + if (last > curr) return false; + last = curr; + } + + return true; + } + public static void main(String[] args) { + int[] a = new int[] { 1, 2, 3, 4 }; + int[] b = new int[] { 1, 5, 3, 4 }; + System.out.println(isSorted(a)); // true + System.out.println(isSorted(b)); // false + } +} +``` + +Примерен код на Java +::: + +### Система за компилация - Gradle + +За Java съществуват две системи за компилация на проекти - Maven и Gradle. Поради лични предпочитания и възможността на Gradle да компилира и други езици (нужна за [@допълнение]), Gradle беше избран. Gradle [@refs-gradle] е една от най-използваните системи за компилация в екосистемата на Java. Основната концепция на Gradle е, че конфигурацията на проектите става чрез код, а по-конкретно - Kotlin DSL и Groovy DSL. Groovy обаче е остаряла технология [@refs-groovy-old] и няма проверки на типовете по време на компилацията, заради което разработчиците на Gradle и обществото от Java разработчици препоръчват употребата на Kotlin DSL. Заради това този проект ще бъде използван Kotlin DSL. + +::: {.figure} + +```c++ +plugins { + kotlin("jvm") version "1.9.0"; + application; +} + +group = "com.example"; +version = "1.0-SNAPSHOT"; + +repositories { + mavenCentral(); +} + +dependencies { + implementation(kotlin("stdlib")); + testImplementation(kotlin("test")); + testImplementation("junit:junit:4.13.2"); +} + +application { + mainClass.set("com.example.MainKt"); +} + +tasks { + test { + useJUnit(); + } +} +``` + +Примерна конфигурация на Gradle проект чрез Kotlin DSL +::: + +---- + +## Избор на метод за интерпретиране + +От разгледаните в [@методи-за-изпълнение-на-код] типове интерпретатори в този проект ще бъде имплементиран хибриден интерпретатор. Като обобщение, хибридният интерпретатор не изпълнява директно програмния език, а компилира езика към междинен език, който след това бива изпълнен. Тази архитектура беше избрана, защото: + +- Изпълнението на кода е по-бързо, на цената на нуждата от компилация на кода преди изпълнението му +- Възможността на замяна на компилатора с по-ефективен без промяна на изпълнителя на код + +За да бъде използвана тази архитектура обаче трябва да бъде създаден междинен език. За този проект беше създаден асемблероподобен език (описан по-подробно в [@междинен-език-и-неговото-изпълнение]), който да бъде интерпретиран. Решението да е асемблероподобен език е мотивирано от факта, че алгоритъма за изпълнение на поредица от инструкции е прост и ефективен - цикъл с таблица от повиквания за всяка инструкция, както и от факта, че компилацията към асемблероподобен език е сравнително лесно. + +# Реализация на интерпретатора + +Архитектурата на интерпретатора се състои от няколко основни компонента: + +- Управление на средата на изпълнение - т. нар. „Environment“ +- Имплементация на операциите с JavaScript стойностите +- Междинният език и неговият изпълнител +- Събитиен цикъл (event loop) +- Система за съхраняване и предоставяне на данни за отстраняване на грешки + +## Компоненти на проекта + +Проекта се дели на няколко основни части: + +- common - Общи класове +- runtime - Интерпретатор +- compilation - Компилатор (описан в [@допълнение]) +- libs - Стандартни библиотеки (описан в [@допълнение]) +- repl - Команден ред (описан в [@допълнение]) + +Предимството на тази архитектура е, че интерпретатора и компилатора са отделни - интерпретатора не знае какъв език интерпретира, докато компилатора работи независимо от интерпретатора. От това следва, че компилатора може да бъде заменен с по-добър такъв, или компилатор на различен език, но интерпретатора ще продължава да работи. + +Стандартната библиотека и командния ред са за удобството на потребителите и програмистите, които ще използват проекта. + +![Отделните компоненти и зависимостите помежду им](./img/dependencies.svg "dependencies") + +## Средата на изпълнение + +Това е най-простият, но и най-важният механизъм на интерпретатора. Той позволява персонализацията на средата на изпълнение на JavaScript кода, както и изолацията на две JavaScript среди на изпълнение. Средата на изпълнение на практика е регистър от добре познати флагови Java обекти, срещу които могат да стоят произволни стойности. Не се използват низове като идентификаторите на тези стойности, понеже може да има конфликти с ключовете - една библиотека може да използва ключа „test“ за референция към тестовите инструменти, докато потребителя може да използва същия ключ за експериментални цели. + +Средата за изпълнение може да бъде и наследявана от друга, като новата среда на изпълнение може да скрива ключове на родителя си, да променя стойностите срещу ключове на родителя си без да променя самите стойности в родителя си и да добавя собствени независими ключове. Тази система за наследяване е вдъхновена от системата за прототипи на JavaScript ([@обекти]). + +Тази структура от данни се използва в интерпретатора за съхранение и взимане на следните данни: + +- Стека на извикванията (`Frame.KEY`) +- Максимална дълбочина на стека на извикванията (`Frame.MAX_STACK_COUNT`) +- Флаг, скриващ стека на извикванията на дадена среда от общия стек на извиквания в диагностичните съобщения (`Frame.HIDE_STACK`) +- Прототипите на елементарните стойности (`Value.BOOL_PROTO`, `Value.NUMBER_PROTO`, ...) +- Прототипите на обектите, масивите и грешките (`Value.OBJECT_PROTO`, `Value.ARRAY_PROTO`, ...) +- Глобалния обект (`Value.GLOBAL`) +- Таблицата на присъщите стойности (`Value.INTRINSICS`) +- Данните за диагностика (`DebugHandler.KEY`) +- Флаг, скриващ кода в тази среда от отстранителя на грешки (`DebugHandler.IGNORE`) +- Референция към събитийния цикъл, отговорен за тази среда на изпълнение (`EventLoop.KEY`) +- Референция към настоящия компилатор (`Compiler.KEY`) + +---- + +### Интерфейс за диагностика + +За разработката на едно приложение е изключително важно интерпретатора да има възможността да предоставя данни и интерфейс за диагностика. В нормалното действие на интерпретатора, в средата за изпълнение не присъства подобна система, но разработчика може да реши, в замяна на производителност, да въведе система за диагностика. + +В този интерпретатор `DebugHandler` е интерфейса, който се използва като интерфейс за диагностика. Той съдържа методи, който се повикват при дадени събития (фиг. [@debug-event-methods]), както и метода `getMap`, чрез който всички останали потребители на дадената среда могат да вземат „картата“ на дадена функция (т.е., хранилище на съответстващите местоположения на кода на всяка инструкция). + +Този интерфейс позволява имплементацията на традиционен дебъгер, който може да бъде използван в традиционни среди за разработка (коментира се примерна имплементация в [@допълнение]). Имплементациите на `DebugHandler` могат да бъде свързана с дадена среда за изпълнение чрез ключа `DebugHandler.KEY`. + +:::{.figure id=debug-event-methods} +Име на метода | Описание +-----------------|--- +`onSourceLoad` | Извиква се от компилатора след компилацията с оригиналния код, които е бил компилиран +`onFunctionLoad` | Извиква се при компилирането на дадена функция с тялото на функцията и картата на тялото +`onInstruction` | Извиква се от интерпретатора веднага след изпълнението на дадена функция +`onFramePush` | Извиква се от интерпретатора, когато даден кадър се добавя към стека на извикванията +`onFramePop` | Извиква се от интерпретатора, когато даден кадър се премахва от стека на извикванията + +Методи, съответстващи на събития в интерпретатора +::: + +---- + +## Имплементация на JavaScript стойностите + +За JavaScript интерпретатора е нужно да бъде създадена система за управление на JavaScript стойности. Тази система ще бъде използвана както от JavaScript кода, така и от потребителски код, за да се създават и управляват JavaScript стойности според Ecma стандарта. + +### Структура на Java класовете + +![Йерархия на основните класове за JavaScript стойности](value-class-hierarchy.svg "class-hierarchy"){} + +В този проект всички JavaScript стойности имплементират един основен интерфейс - „Value“. Този интерфейс съдържа методи, които съответстват на всяка операция, която може да бъде извършена с JavaScript стойност ([@value-methods-table]). Двата подтипа на „Value“ са интерфейса „PrimitiveValue“ и класа „ObjectValue“. „PrimitiveValue“ е интерфейса, който всички типове за елементарни стойности наслеждават. „ObjectValue“ пък е основният клас за всички типове за стойности-обекти. „ObjectValue“ има два основни подкласа - „ArrayLikeValue“, основен клас на всички класове за обектите, които се държат като масиви и „FunctionValue“, основният клас на всички класове за JavaScript функциите. + +### Празни стойности + +Празните стойности са `null` и `undefined`. Тези стойности, както и в други езици, се използват за отбелязването на липса на стойност. JavaScript предоставя две празни стойности, защото `undefined`, преди въвеждането на `in` оператора, е бил единственият начин да се разбере дали член на даден обект съществува или не. Тези стойности имат следните общи свойства: + +- Когато се направи опит да се достъпи техен член се хвърля грешка +- Не са истинни (превръщат се към булевата стойност `false`) +- Превръщат се към специалната числова стойност `NaN` +- Нямат прототип и при опит той да бъде взет или зададен се хвърля грешка + +За стойността `undefined` е специално заделен типовия низ (низа, върнат при операцията `typeof` върху стойността) „undefined“ докато `null` споделя „object“ с останалите обекти. + +В кода и двете стойности са имплементирани с класа `VoidValue`, който може да бъде използван за да бъдат създадени и други празни стойности. + +---- + +### Елементарни стойности + +В JavaScript елементарните стойности са всички стойности, които не са обекти. Такива стойности не могат да съдържат референция към друга JavaScript стойност. Такива стойности биват: + +- Празни стойности (null и undefined) +- Булеви стойности (true и false) +- Числови стойности (по стандарт 64-битови числа с плаваща запетая) +- Низове (по стандарт съставени от 16-битови символи) +- Символи (маркерни стойности, обяснени по-надолу) + +Всички елементарни стойности (освен празните стойности) имат константен прототип в зависимост от типа си, но нямат собствени членове (изключение прави низа, който симулира структурата на константен масив от символи). + +### Символи + +Символите са особен тим от елементарни стойности в JavaScript, въведен в по-късните разработки на Ecma. Понеже обаче символите се използват от голяма част от модерният JavaScript код, беше взето решение те да бъдат имплементирани в интерпретатора въпреки това. + +Символите са флагови стойности, които потребителя може да създаде сам. Отличаващото ги свойство е, че при всяко създаване на символ се създава уникален символ, т.е. не може по-късно да бъде създаден нов такъв символ. Другото свойство на низовете е, че могат да бъдат ключове на членове на обекти. + +На практика низовете се използват за добавянето на нови полета в обекти, без те да пречат на стар код, който не знае за съществуването на (тези) символи. + +В интерпретатора символите са имплементирани в класа `SymbolValue`, който имплементира интерфейса `PrimiteValue`. + +### Числени и целочислени стойности + +Числата в този интерпретатор са представени чрез основния интерфейс `NumberValue`. Това включва числата с плаваща запетая, както и целите числа. Въпреки че стандарта не уточнява тип за цели числа, този интерпретатор има прозрачна имплементация на целите числа (т.е., кода не може и не трябва да може да различи целите числа от числата с плаваща запетая). + +В кода, целите числа са имплементирани в класа `IntValue`, а числата с плаваща запетая - класа `DoubleValue`. Тези класове обаче не могат да бъдат инстанцирани ръчно - трябва да бъде използван метода `NumberValue.of`, който създава една от двете инстанции, в зависимост от това дали подаденото число може да бъде представено с 32-битово число. + +### Обекти + +Обектите са основополагаща концепция в JavaScript. В основата на обектите стои списък от уникални низове (т. нар. ключове) и съответстващият им член [@членове] (съществува и паралелен списък със списък от ключове-символи към членове). Обектите се използват най-вече за съхранение на няколко подстойности под формата на структурирани данни (подобно на класовете на Java), но могат да бъдат използвани и като хеш-таблици. + +Обектите по подразбиране са отключени за всякакъв вид промяна, но потребителя може да зададе различен режим на обекта, който ограничава дадени типове промени на обекта. Тези режими са следните: + +- Нормален - всякакъв тип промяна е позволен +- Режим без разширения - прототипа на обекта не може да бъде променян и нови полета не могат да му бъдат добавяне +- Режим без пренастройвани (запечатване) - освен ограниченията от предишният режим, полетата не могат да бъдат трити и пренастройвани +- Режим на заключване (замразяване) - обекта става на практика константа, нито едно негово свойство не може да бъде променено + +JavaScript кода може да променя режима от по-свободен към по-ограничаващ, но не и обратно. Това разрешава на разработчицитие да осигурят дадено ниво на константност на данните на обектите си. + +Накрая, обектите имат прототипи. Прототипа може да е `null` или обект. При търсенето на член, може обект да няма дадения ключ. Тогава търсенето продължава в прототипа на обекта. Понеже обаче прототипа е обект на практика съществува свързан списък от обекти, или т. нар. прототипна верига. Прототипите са в основата на обектно-ориентираното програмиране в JavaScript, понеже може чрез прототипите да бъде дефиниран един основен обект, който да съдържа всички функции за даден „тип“ обекти, а всички обекти от този „тип“ просто имат този основен обект като прототип. Освен това обаче прототипите могат да бъдат използвани и за осъществяване на наследство на обекти - обекта „AnimalPrototype“, който съдържа члена „speak“ може да бъде прототип на обекта „DogPrototype“, който съдържа члена „bark“. При тази конфигурация, „DogPrototype“ де факто има и двете полета - „speak“ и „bark“. + +В кода обектите са имплементирани чрез класа „ObjectValue“. Този клас съдържа четири хеш-таблици, които съдържат съответно низовите полета, низовите свойства, символните полета и символните свойства, като тези таблици общо съхраняват всички членове на обекта. Освен това обекта съдържа и наредени хеш-таблици от низовите и символните ключове, като на всеки ключ е съпоставена булева стойност - дали ключа е свойство. Хеш-таблиците от ключовете са наредени, понеже стандарта изисква обектите да съхраняват ключовете в реда, в който са били вмъкнати в обекта. + +Прототипа не се съхранява директно като референция към обект, а като функционален интерфейс, който може да върне различен обект за различна среда. Това позволява обекти да бъдат използвани от няколко среди без конфликти. + +Накрая, обекта съхранява и режима на ограничение в едно поле от тип `State`, което е енумерация. Въпреки, че е възможно да бъде само булева стойност, която определя дали може обекта да се разшири, замразяването и запечатването на обект може да стане само с обхождане на всички полета, което е по-бавно за по-големите обекти. + +### Функции + +В JavaScript функциите не са нищо повече от обекти, които могат да бъдат извикани. Функциите могат да бъдат извикани по два начина (или по-точно, поддържат две допълнителни операции, които никой друг тип стойности не поддържа): + +- Чрез прилагане („applying“) - това е нормалният начин за извикване на функция. „this“ аргумента е `undefined`, освен когато функцията не бъде извикана рефлективно или синтактично като член на даден обект (тоест чрез синтаксиса `obj.key(a, b, c)`) +- Чрез конструиране („constructing“) - при този режим на извикване функцията се изпълнява като конструктор на обект - „this“ аргумента на функцията става обект, а върнатата стойност на функцията е именно същия обект + +Функциите в този интерпретатор се делят на два подтипа - JavaScript функции и Java функции. JavaScript функциите (имплементирани в класа `CodeFunction`) се състоят от JavaScript код, който се изпълнява при всяко извикване на функцията, докато Java функциите (имплементирани в класа `NativeFunction`) са функции, които съдържат функционален интерфейс като поле, което бива извикано при всяко извикване на функцията. Употребата на JS функциите е очевидна, а Java функциите се използват, за да предоставят достъп до функционалност на JavaScript функциите, до който иначе те не биха имали достъп - такива функции могат да бъдат съпоставени със системните извиквания на C. + +### Масиви и масивоподобни обекти + +Масивите в JavaScript не са нищо повече от обект, който има целочислени ключове, представени чрез низове, както и специалният член „length“, който съдържа броя на елементите на масива. Понеже обаче съхранението на данните на масива заедно с останалите членове е крайно неефективно, този интерпретатор има специален подобект за масивоподобните обекти - `ArrayLikeObject`. Такива обекти фалшифицират виртуални полета с целочислени ключове, съответстващи на представлявания масив, както и полето „length“, което отразява дължината на представлявания масив. + +Класа `ArrayLikeObject` обаче не конкретизира по какъв начин се съхраняват данните. Това се дължи на факта, че може да има много масивоподобни данни - буфени, аргументите на функцията, нормален буферен списък от данни както и много други. В конкретния (и най-честия) случай на масивоподобните данни - прост списък от JavaScript функции, има създаден клас `ArrayObject`, който е имплементиран чрез масив, брояч на дължината и логика за динамичното уголемяване на масива. За да бъдат съхранени дупките (фиг. [@example-array-holes]) в масива се използва Java флаговата стойност „null“. + +:::{.figure id=example-array-holes} +```js +var arr = []; +arr[0] = 5; +arr[1] = 6; +arr[4] = 8; +arr[12] = 3; +console.log(arr); // [5, 6, , 8, 3] +``` +Пример за масив с дупки +::: + +Освен за по-ефективното съхраняване на данните на масива, има нужда за подобна оптимизациа защото се елиминира нуждата от превръщане на низове към цели числа и обратно при търсенето на елементи от масива (в случаите, в които при търсенето на член от масива се подава числова стойност). + +### Членове + +Във всеки обект фигурират членове. Това са контейнерите, които съхраняват JavaScript стойностите в обектите. Членовете биват два вида: полета и свойства. Полетата са по-простият тип членове - съдържат само JS стойност, докато свойствата са членове, чиято стойност се задава и взима чрез две функции - „get“ за взимане на стойността и „set“ за съхранението на стойността. + +Освен това, членовете имат следните настройки: + +- Обходимост (enumerable) - при обхождане на ключовете на обекта, ключа на този член ще бъде обходен само ако тази настройка е `true` +- Настройваемост (configurable) - разрешава промените на настройките на члена само ако тази настройка е `true` +- Презаписваемост (writable) - присъства само при полетата, разрешава презаписването на стойност само ако тази настройка е `true` + +За настройка се приемат следните действия: + +- Промяната на една от тези настройки, освен ако презаписваемо, но ненастройваемо поле се направи непрезаписваемо +- Изтриване на полето от обекта + +Членовете са дефинирани чрез интерфейса „Member“, полетата чрез класа „FieldMember“, а свойствата чрез „PropertyMember“. Понеже полетата могат да бъдат виртуални и контролирани от Java код, е осигурена базова имплементация за съвсем прости полета, които са само контейнер за JavaScript стойност, в класа „SimpleFieldMember“, докато по-сложна логика може да създаде собствена имплементация на „FieldMember“ класа (на този механизъм са базирани масивите). + +---- + +:::{.figure id=value-methods-table} + +Име на операцията | Аргументи | Описание +--------------------|---------------|----------- +Взимане на тип | val | Връща низ, представляващ типа на аргумента. Тази операция може да върне следните типове: „undefined“, „boolean“, „string“, „number“, „object“, „function“ или „symbol“ +Прилагане | func, self, ...args | Ако `func` е функция я прилага със „this“ аргумента равен на `self` и останалите аргументи `args`. Стойността, която тази операция връща е върнатата стойност от функцията +Конструиране | func, target, ...args | Ако `func` е функция я конструира с оригиналния конструктор [@refs-new-target] равен на `target` и останалите аргументи `args`. Стойността, която тази операция връща е върнатата стойност от функцията +Превръщане в елементарна стойност | val | Ако стойността не е елементарна, в зависимост от наличните полета, превръща стойността в низ или число ([@refs-type-coercion]) +Превръщане в низ | val | Превръща стойността в елементарна, а след това елементарната превръща в низ +Превръщане в число | val | Превръща стойността в елементарна, а след това елементарната превръща в число +Превръщане в булева стойност | val | Всички стойности се превръщат в `true`, с изключение на следните: 0, NaN, false, празен низ, undefined, null +Проверка на инстанция | obj, func | Проверява дали `func.prototype` е част от веригата от прототипи на `obj` +Взимане на член | obj, key | Намира члена на обект, като включва и членовете на прототипа, както и неговия прототип и така нататък +Проверка на ключ | obj, key | Като взимането на член, но вместо да върне стойността на члена, връща дали член с такъв ключ съществува в обекта +Съхраняване на член | obj, key, val | Ако `obj` (или някой негов прототип) има свойство с ключ `key`, задава стойността на свойството. Иначе създава ново поле с ключ `key` и стойност `val` със всички настройки включени +Взимане на собствен член | obj, key | Като взимането на член, но игнорира веригата от прототипе +Взимане на собствен ключ | obj, key | Като взимането на собствен член, но вместо да върне стойността на члена, връща дали член с такъв ключ съществува в обекта +Взимане на низови ключове | obj | Взима списък от всички низови ключове на стойността, като в зависимост от аргументите ще бъдат върнати само обходимите или всички членове, само собствените членове или включва и всички от прототипната верига +Взимане на символни ключове | obj | Взима списък от всички ключове на стойността. Има подобни опции на предишната операция +Дефиниране на собствено низово поле | obj, key, value, writable, configurable, enumerable | Дефинира даденото поле с дадените настройки +Дефиниране на собствено низово свойство | obj, key, get, set, configurable, enumerable | Дефинира даденото поле с дадените настройки +Дефиниране на собствено символно поле | obj, key, value, writable, configurable, enumerable | Дефинира даденото поле с дадените настройки +Дефиниране на собствено символно свойство | obj, key, get, set, configurable, enumerable | Дефинира даденото поле с дадените настройки +Изтриване на собствен член | obj, key | Изтрива дадения член от таблицата на обекта +Задаване на прототип | obj, proto | Задава нов прототип `proto` на `obj`. Трябва `proto` да е `null`, `undefined` или обект +Визмане на прототип | obj | Връща прототипа на дадения `obj` +Визмане на обектно състояние | obj | За елементарните стойности връща състояние „замръзнал“. За обектите връща състоянието на ограничение +Забраняване на разширения | obj | Ако това няма да фигурира в нивото на ограничение, поставя стойността в режим на „забранени разширения“ +Запечатване | obj | Ако това няма да фигурира в нивото на ограничение, поставя стойността в режим на „запечатване“ +Замръзване | obj | Ако това няма да фигурира в нивото на ограничение, поставя стойността в режим на „замръзване“ +Сбор | a, b | Превръща `a` и `b` в елементарни стойности. Ако и двете елементарни стойности са числа връща сбора им, иначе превръща двете стойности в низове и ги свързва в един +Разлика | a, b | Превръща двете стойности в числа и извършва аритметичното изваждане на `b` от `a` +Делимо | a, b | Превръща двете стойности в числа и извършва аритметичното деление на `b` с `a` +Произведени | a, b | Превръща двете стойности в числа и извършва аритметичното умножение на `b` и `a` +Деление с остатък | a, b | Превръща двете стойности в числа и извършва аритметичното деление с остатък на `b` с `a` +Побитово и | a, b | Превръща двете стойности в цели числа със знак и извършва побитовата „и“ операция +Побитово или | a, b | Превръща двете стойности в цели числа със знак и извършва побитовата „или“ операция +Побитово изкл. или | a, b | Превръща двете стойности в цели числа със знак и извършва побитовата „изключително или“ операция +Стриктно равенство | a, b | Извършва алгоритъма за стриктно сравнение на две стойности, описан в [@refs-strict-equals] +Стриктно неравенство | a, b | Връща обратната стойност на стриктното равенство +„Хлабаво“ равенство | a, b | Извършва алгоритъма за „хлабаво“ сравнение на две стойности, описан в [@refs-loose-equals] +„Хлабаво“ неравенство | a, b | Връща обратната стойност на хлабавото равенство +По-голямо | a, b | Превръща двете стойности в елементарни. Ако и двете елементарни стойности са низове ги сравнява символ по символ, иначе стойностите се превръщат в числа и тогава се сравняват. Върнатата стойност е `true` ако `a` e по-голяма от `b` +По-голямо или равно | a, b | Използва се алгоритъма за сравнение, описан по-горе. Върнатата стойност е `true` ако стойността на `a` e по-голяма или равна на тази на `b` +По-малко | a, b | Използва се алгоритъма за сравнение, описан по-горе. Върнатата стойност е `true` ако стойността на `a` e по-малка от тази на `b` +По-малко или равно | a, b | Използва се алгоритъма за сравнение, описан по-горе. Върнатата стойност е `true` ако стойността на `a` e по-малка или равна от тази на `b` +Побитово обръщане | val | Превръща `val` в 32-битово число със знак и връща същото число с обърнати битове +Булево обръщане | val | Превръща `val` в булева стойност и връща противоположната булева стойност на превърнатата +Знаково обръщане | val | Превръща `val` в число и връща противоположната (-val) стойност +Побитово ляво изместване на битовете | a, b | Превръща стойностите в 32-битови числа със знак и извършва битово преместване наляво на `a` с `b` бита +Побитово дясно изместване на битовете | a, b | Превръща стойностите в 32-битови числа със знак и извършва битово преместване надясно на `a` с `b` бита +Побитово ляво изместване на битовете (със знака) | a, b | Превръща стойностите в 32-битови числа без знак и извършва битово преместване надясно на `a` с `b` бита + +Списък от всички операции, които могат да бъдат извършвани със JavaScript стойностите +::: + +---- + +## Междинен език и неговото изпълнение + +За да се реализира избраната хибридна архитектура на интерпретатора беше нужно да се създаде междинен език. Езикът, който беше създаден е асемблероподобен език, базиран на операции върху стека. Всяка JavaScript функция има свой списък от инструкции, наречен „тяло“ на функцията, което се изпълнява всеки път, когато функцията бъде извикана. + +При изпълнението на тялото на една функция се създава т. нар. „кадър“, който е структурата от данни, отговорна за съхранението на състоянието на изпълнението на функцията. Параметрите, които се записват в един кадър са показани на фигура [@frame-storage]: + +:::{.figure id=frame-storage} + +Име | Начална стойност | Описание +----------------- | ---------------- | --------------------- +Стек | Празен масив | Структура, описана в [@стек] +Указател на стека | 0 | Съхранява колко елемента от стека се използват +Указател на кода | 0 | Съхранява инструкцията, до която е достигнало изпълнението на кода +Променливи | | Структура, описана в [@локални-променливи] +Аргументи | | Подадените при извикването на функцията аргументи +Функция | | Функцията, чието тяло изпълнява този кадър + +Информацията, съхранявана в един кадър +::: + +---- + +### Стек + +Стекът е структура, която може да бъде сравнена с метафорична „купчина“ от елементи. Елементи могат да бъдат добавяни върху стека и да бъдат отнемани от върха на стека. Тази структура от данни се нарича „последните вътре са първите вън“ (на англ. LIFO, фиг. [@stack-lifo]). + +Имплементацията на тази структура от данни е лесно осъществима с масив и указател към първия свободен елемент от стека. При добавяне на елемент в масива се слага елемента на мястото, към което сочи указателя, след което указателя се увеличава с едно. За да се премахне елемент е нужно само да се намали указателя. Този метод беше използван и за имплементацията на стековете за кадрите. + +![Операции със стек](https://upload.wikimedia.org/wikipedia/commons/e/e4/Lifo_stack.svg "stack-lifo"){id=stack-lifo} + +![Стек, базиран на масив](./img/stack-arr.png "stack-arr") + +:::{.figure} +```java +class ArrayStack { + int stack[] = new int[16]; + int ptr = 0; + + void push(int val) { + if (this.ptr >= this.stack.length) { + int newStack = new int[this.stack.length * 2]; + System.arraycopy(this.stack, 0, newStack, 0, this.stack.length); + this.stack = newStack; + } + + this.stack[this.ptr] = val; + this.ptr++; + } + int pop() { + this.ptr--; + return this.stach[this.ptr]; + } +} +``` +Примерна имплементация на добавяне и премахване на елемент от стек, базиран на масив +::: + +### Локални променливи + +Освен стека, кода има достъп и до променливи. Променливите, за разлика от стека, са контейнери за стойности, които поддържат случаен достъп (всяка променлива може да бъде достъпена по всяко време). Всяка програма трябва да дефинира броя променливи, от които се нуждае, като при всяко създаване на кадър ще бъдат създавани съответния брой променливи. + +Понеже функциите могат да бъдат „вграждани“ една в друга, съществува механизъм, чрез който функция, вградена в друга, може да използва променливите на родителя си. Това се постига чрез въвеждането на 3 вида променливи: + +- локални - използваеми само за текущата функция +- уловими - използваеми както за текущата функция, така и за вградените в текущата функция функции +- уловени - референции към даден набор от уловимите и уловените променливи на родителя на текущата функция + +Променливите могат да бъдат и индексирани, като отрицателните индекси съответстват на уловените променливи (като -1 е първата уловена, -2 е втората и т.н.), а положителните индекси индексират първо локалните, а след това уловимите променливи, все едно са един масив (ако има 3 локални и 5 уловими променливи, 0 е първата локална, 3 е първата уловима, а 8 е първият индекс, който е извън границите) + +В програмен код, променливите са дефинирани като три масива в класа `Frame`. Локалните променливи са масив от класа `Value` (`Value[]`), докато уловените и уловимите променливи са масиви от масиви с една стойност от типа `Value` (`Value[][1]`). Използва се такава структура за уловимите и уловените променливи, за да бъде възможно споделянето на една променлива от няколко подфункции. + +При създаването на вградена функция, която улавя променливи, се подава масив от уловими и уловени променливи, които функцията е декларирала, че уловява. Този масив ще съответства директно на масива `captures`, използван в кода. + +:::{.figure} +```java +class Frame { + // ... + public final Value[][] captures; + public final Value[] locals; + public final Value[][] capturables; + + // Взима стойността на променлива + public Value getVar(int i) { + if (i < 0) return this.captures[-i - 1][0]; + else if (i < locals.length) return this.locals[i]; + else return this.capturables[i - this.locals.length][0]; + } + // Задава нова стойност на дадената променлива + public Value setVar(int i, Value val) { + if (i < 0) return this.captures[-i - 1][0] = val; + else if (i < this.locals.length) return this.locals[i] = val; + else return this.capturables[i - this.locals.length][0] = val; + } + // Улавя дадената променлива + public Value[] captureVar(int i) { + if (i < 0) return this.captures[~i]; + if (i >= this.locals.length) { + return this.capturables[i - this.locals.length]; + } + else throw new RuntimeException("Illegal capture"); + } + + // ... +} + +``` + +Опростена версия на кода за управление на променливите +::: + +### Инструкциите и тяхното изпълнение + +Инструкцията е основната единица, която съставя тялото на една функция. Всяка инструкцията се състои от тип и даден брой операнди в съответствие на типа на инструкцията. Зад всяка инструкция стои дадена логика, която се изпълнява, когато интерпретатора „изпълни“ интрукцията. Примери за инструкции могат да бъдат „Събери стойностите на операнд 1 и операнд 2 и изпечатай стойността“ или „Задай нова стойност програмния брояч равна на операнд 1“. Във таблица [@instruction-set-table] са описани всички инструкции. + +За да се изпълнят инструкциите обаче е нужен програмен брояч. Програмният брояч съхранява индекса на инструкцията, до която е стигнал интерпретатора. Броячът се използва, за да е възможно прекъсването на изпълнението на тялото, както и скоковете в тялото от други инструкции. + +Накрая, самото изпълнение на една инструкция се свежда до цикъл, в който се прави избор на метода, който изпълнява логиката на инструкцията според нейния тип. Примерен код, който прави това е показан на фигура [@example-instr-exec], а примерен код, който използва примерният интерпретатор е показан на фигура [@example-instr-usage] + +:::{.figure id=example-instr-exec} +```java +public class Frame { + private Value executeLoadNumber(Instruction instruction) { + double value = instruction.getOperand(0); + this.stackPush(NumberValue.of(instruction.value)); + this.codePtr++; + return null; + } + private Value executeDuplicate(Instruction instruction) { + Value val = this.stackPeek(); + this.stackPush(val); + this.codePtr++; + return null; + } + private Value executeAdd(Instruction instruction) { + Value a = this.stackPop(); + Value b = this.stackPop(); + + this.stackPush(NumberValue.of(a.toNumber() + b.toNumber())); + this.codePtr++; + + return null; + } + private Value executeJmp(Instruction instruction) { + this.codePtr += instruction.getOperand(0); + return null; + } + private Value executeRet(Instruction instruction) { + if (this.stackPtr == 0) return Value.UNDDEFINED; + else return this.stackPop(); + } + private Value executePrint(Instruction instruction) { + System.out.println(this.stackPop()); + this.stackPtr++; + return null; + } + + public Value executeNext() { + switch (this.body[this.codePtr].type) { + case LOAD_NUMBER: return executeLoadNumber(instruction); + case ADD: return executeAdd(instruction); + case JMP: return executeJmp(instruction); + case RET: return executeRet(instruction); + case DUP: return executeDuplicate(instruction); + case PRINT: return executePrint(instruction); + } + } + public Value executeUntilReturn() { + while (true) { + Value res = executeNext(); + if (res != null) return res; + } + } +} +``` +Опростена версия на интерпретатора +::: + +:::{.figure id=example-instr-usage} +```txt +; Добавя 10 към стека +LOAD_NUMBER 10 +; Добадя 1 към стека +LOAD_NUMBER 1 +; Добавя първият елемент на +; стека с новодобавената единица +ADD +; Клонира резултата, за +; да не бъде загубен +DUP +; Печата клонираният резултат +PRINT +; Връща се към LOAD_NUMBER 1 +JMP -4 +; Преди скока на стека вече е останал +; един елемент, с едно по-голям от предишният +``` +```java +int temp = 10; +while (true) { + temp = temp + 1; + System.out.println(temp); +} +``` +--- +Примерен код, използващ примерния интерпретатор по-горе, и негов еквивалент, написан на Java +::: + +### Възможност за прекъсване на изпълнението на кадър + +Кадрите в този интерпретатор дават възможността инструкциите им да бъдат изпълнявани по една наведнъж. Това позволява на потребител на интерпретатора да прекъснат изпълнението на даден кадър, като по-късно изпълнението на същия кадър може да бъде възобновено по всяко време, като кадъра запазва състоянието си, все едно кода никога не е спирал. + +В основата на този механизъм стои идеята, че кадъра изпълнява инструкциите си една наведнъж (тоест метода `next` изпълнява само една инструкция). Това до известна степен усложнява имплементацията, но позволява тази гъвкавост. + +Освен самата възможност за прекъсване на изпълнението на кадър, това позволява и вмъкването на върната стойност от предишна инструкция, индуцирането на върната стойност от кадъра ,както и индуцирането на изключение, хвърлена от кадъра. Това позволява имплементирането на JavaScript генератори без допълнителна компилационна стъпка. + +:::{.figure} +```java +Frame frame = ...; +int i = 0; + +while (true) { + var res = frame.next(); + + if (res != null) { + System.out.println("Returned value: " + res.toReadable(env)); + break; + } + + if (i++ % 10 == 0) { + System.out.println(i + " instructions executed, sleeping 2 seconds..."); + Thread.sleep(2000); + } +} +``` + +Пример за прекъсване на кадър, по средата на неговото изпълнение +::: + +### Набор от инструкции + +За този интерпретатор беше създаден набор от инструкции, чрез които код, написан чрез тях да може да обработва стека, да управлява изпълнението на кода и да борави с JavaScript стойности. + +:::{.figure id=instruction-set-table} +Име | Операнди | Аргументи от стека | Описание +-------------|---------------|--------------------|---- +RETURN | | value | Приключва изпълнението на функцията с върната стойност `value` +NOP | | | Не прави нищо +THROW | | error | Създава изключение [@система-за-изключения] от `error` +THROW_SYNTAX | msg | | Създава изключение за синтактична грешка с даденото съобщение `msg` +DELETE | | key, object | Изпълнява логиката за премахване на члена `key` от `object`. Връща булева стойност на стека, в зависимост от резултата от операцията +TRY_START | catchStart, finallyStart, end | | Създава нова зона за изпълнение на защитен код ([@защитено-изпълнение-на-кода]) и започва тестовия регион от следващата функция +TRY_END | | | Извършва изход от текущия регион на защитения режим по нормален начин +CALL | argN, hasSelf | args, function, this | Прилага `function` с `this` и аргументи `args`. Аргументите на извикването се взимат от стека в обратен ред (първите вътре са първите излезли), за да се запази ред на изпълнение +CALL_NEW | argN | args, function | Конструира `function` с оригинален конструктор същата функция и аргументи `args`. Аргументите се взимат от стека както CALL ги взима +JMP_IF | offset | value | Ако `value` е истинна стойност, `offset` се прибавя към `codePtr` +JMP_IFN | offset | value | Ако `value` е лъжовна стойност, `offset` се прибавя към `codePtr` +JMP | offset | | Добавя `offset` към `codePtr` +PUSH_UNDEFINED | | | Добавя `undefined` към стека +PUSH_NULL | | | Добавя `null` към стека +PUSH_BOOL | value | | Добавя булевият операнд `value` към стека +PUSH_NUMBER | value | | Добавя 64-битовото IEEE 754 числото с плаваща запетая `value` към стека +PUSH_STRING | value | | Добавя низа от символи `value` към стека +DUP | count, offset | | Добавя елемента `offset` на брой елементи преди последния елемент на стека `count` на брой пъти върху стека +DISCARD | | | Премахва последната стойност от стека без да прави нищо с нея +LOAD_FUNC | id, name, captures... | | Създава функция с даденото ID, име и уловени променливи и я добавя към стека. За повече информация, вижте [@функции] +LOAD_ARR | count | | Създава масив с `count` на брой празни елемента и го добавя към стека +LOAD_OBJ | | | Създава празен обект и го добавя към стека +LOAD_GLOB | | | Добавя глобалният обект към стека +LOAD_INSTINSIC | name | | Добавя вътрешната стойност с даденото име `name` към стека +LOAD_ARG | i | | Добавя стойността на `i`-тия аргумент от текущото извикване към стека +LOAD_ARGS_N | | | Добавя броят на аргументите на текущото извикване към стека +LOAD_CALLED | | | Добавя стойността на изпълняванта функция към стека +LOAD_THIS | | | Добавя „this“ аргумента към стека +LOAD_ERROR | | | Когато изпълнението на програмата е в регион на защитено изпълнение в състояние на хванато изключение, тази инструкция добавя хванатото изключение към стека +LOAD_VAR | i | | Зарежда променливата с индекс `i` и добавя стойността ѝ към стека +LOAD_MEMBER | | key, object | Взима члена `key` на `object` и го добавя към стека +LOAD_MEMBER_STR | key | object | Еквивалент на LOAD_MEMBER, но с константен низов ключ `key` +LOAD_MEMBER_INT | key | object | Еквивалент на LOAD_MEMBER, но с константен числов ключ `key` +STORE_VAR | i, keep | value | Съхранява `value` в променливата с индекс `i`. Ако `keep` е `true`, добавя `value` към стека +STORE_VAR | i, keep | value | Съхранява `value` в променливата с индекс `i`. Ако `keep` е `true`, добавя `value` към стека +STORE_MEMBER | keep | value, key, object | Задава нова стойност `value` на члена `key` на `object`. Ако `keep` е `true`, добавя `value` към стека +STORE_MEMBER_STR | key, keep | value, object | Еквивалент на STORE_MEMBER, но с константен низов ключ `key` +STORE_MEMBER_INT | key, keep | value, object | Еквивалент на STORE_MEMBER, но с константен числов ключ `key` +GLOB_GET | name, force | | Зарежда променливата `name` от глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека +GLOB_SET | name, keep, force | value | Съхранява променливата `name` в глобалния обект. Ако `force` е `true` не се хвърля грешка ако променливата не съществува. Стойността на променливата се добавя към стека, ако `keep` е `true` +GLOB_DEF | name | value | Създава променливата `name` със стойност `undefined` в глобавният обект, ако не съществува +OPERATION | type | values... | Изпълнява дадената операция (фиг. [@operations-table]) с даденият брой операнда, които операцията изисква. Добавя стойността при изчислението на операцията към стека + +Набор от инструкции на междинния език (бележка: в колоната с аргументи от стека са показани аргументите в реда, в който се взимат от стека, освен ако друг ред не е уточнен) +::: + +:::{.figure id=operations-table} + +Име | Операнди | Еквивалентна операция +-----------------------|------------|-------------- +ADD | a, b | Сбор +SUBTRACT | a, b | Разлика +DIVIDE | a, b | Деление +MULTIPLY | a, b | Произведение +MODULO | a, b | Остатък от деление +AND | a, b | Побитово и +OR | a, b | Побитово или +XOR | a, b | Побитово изключително или +EQUALS | a, b | Стриктно равенство +NOT_EQUALS | a, b | Стриктно неравенство +LOOSE_EQUALS | a, b | Хлабаво равенство +LOOSE_NOT_EQUALS | a, b | Хлабаво неравенство +GREATER | a, b | По-голямо +GREATER_EQUALS | a, b | По-голямо или равно +LESS | a, b | По-малко +LESS_EQUALS | a, b | По-малко или равно +INVERSE | val | Побитово обръщане +NOT | val | Булево обръщане +POS | val | Превръщане в число +NEG | val | Знаково обръщане +SHIFT_LEFT | a, b | Побитово ляво изместване на битовете +SHIFT_RIGHT | a, b | Побитово дясно изместване на битовете +USHIFT_RIGHT | a, b | Побитово дясно изместване на битовете (със знака) +TYPEOF | val | Взимане на типа +IN | key, obj | Проверка на ключ +INSTANCEOF | obj, class | Проверка на инстанция + +Набор от операциите, които OPERATION инструкцията може да изпълнява (бележка: операндите се взимат от стека в обратен ред - първо се взимат последните, всяка операция добавя стойност на стека или хвърля грешка. За подробно описание на операциите вижте фиг. [@value-methods-table]) + +::: + +## Система за изключения + +Изключенията са основна част от JavaScript. Те се използват за сигнализирането на неочаквано състояние или събитие от кода. Ecma стандарта уточнява два механизъма, чрез които JavaScript кода може да борави с изключения: хвърляне на изключения и изпълняване на код в защитен режим за улавянето на изключение. + +### Създаване на изключения + +Създаването (или „хвърлянето“) на изключение спира изпълнението на текущата функция и продължава изпълнението на програмата в първия обработвач на изключения, или ако няма такива, изпълнението на програмата се прекратява. + +Понеже семантиките за хвърляне на грешка на JavaScript и Java са подобни, беше създадено Java изключение, наречено `EngineException`. То съдържа както стойността, която е хвърлена, така и диагностични данни за стека на повикванията по времето на хвърлянето на грешката. Когато JavaScript кода иска да хвърли грешка, интерпретатора създава служебна Java грешка от типа `EngineException`, която обвива хвърлената грешка. След това създадената грешка просто се хвърля. От друга страна, всеки кадър улавя `EngineException` грешките и добавя текущата точна от изпълнението на кода в събрания от изключението стек от повиквания, като след това отново изхвърля грешката. + +:::{.figure} +```java +class InstructionRunner { + ... + private static Value execThrow( + Environment env, Instruction instr, Frame frame + ) { + throw new EngineException(frame.pop()); + } + ... + public static Value exec( + Environment env, Instruction instr, Frame frame + ) { + switch (instr.type) { + ... + case THROW: return execThrow(env, instr, frame); + ... + } + } +} +class Frame { + ... + public final Value next(...) { + try { + ... + return InstructionRunner.exec(env, instruction, this); + } + catch (EngineException e) { + throw e.add(env, function.name, getExecutionLocation()); + } + } + ... +} + +``` +Код за изхвърляне и „обогатяване“ на JavaScript изключенията +::: + +### Защитено изпълнение на кода + +Другата част от системата за изключения е режима на защитено изпълнение на код. Той позволява улавянето на хвърлени от дадено парче код, както и обработката на дадената грешка. Режима за защитено изпълнение има три часа - тестова част, част за улавяне на грешката и част за финализиране на режима на защитено изпълнение. + +Първо, тестовата част (`try`) се изпълнява. Ако тя бъде изпълнена успешно, програмата продължава в нормалния си ход. Ако обаче тестовата част хвърли грешка, кода продължава в частта за улавяне на грешката (`catch`) се изпълнява. В рамките на тази част кода може да вземе стойността на уловената грешка и да прецени какво да бъде направено според нейната стойност. + +Частта за финализиране на защитеното изпълнение се изпълнява след като кода в предишните две части приключи своята работа. Ако предишните части върнат стойност, хвърлят грешка или направят скоко извън защитената зона, частта за финализиране се изпълнява преди изхода от режима да бъде направен. Ако частта за финализиране излезе необичайно (т.е., не завърши изпълнението си в края на частта на финализиране), изхода от частта на финализиране се използва. + +Няколко защитени зони могат да бъдат вградени една в друга. Тогава, ако вътрешната зона излезе по някакъв начин, външната зона обработва този изход като гореописаният начин. + +В кода, тази логика е описана чрез стек от класа `TryCtx`, който описва състоянието на дадена защитена зона. След изпълнението на дадена инструкция, + +За имплементацията на `try-catch` обаче е нужен по-сложен механизъм, за да бъде запазена възможността за изпълнението на JavaScript инструкциите една по една. В основата на механизма стои + +# Ръководство на потребителя + +Въпреки че не са част от тази дипломна работа, допълнително бяха разработени три библиотеки, които ще бъдат използвани в част от примерите: + +- Компилатор на JavaScript към междинния език, описан в [@междинен-език-и-неговото-изпълнение] +- Имплементация на част от стандартната библиотека за JavaScript, на JavaScript +- Интеграция на Babel за поддръжката на по-нов синтаксис +- Примерна имплементация на команден ред за интерпретатора + +## Компилиране на проекта + +Можете да изтеглите кода на проекта, като и всичко нужно за компилирането му от приложената флашка (в директорията „j2s“) или от Git репозиторията . За компилацията на кода е нужен следния софтуер: + +- OpenJDK 17 SDK +- Gradle 8.10 + +След като софтуера е инсталиран е достатъчно командата „gradle build“ да бъде изпълнена в главната директория на проекта. След като командата приключи своето изпълнение, проектът ще се е компилирал. В директорията „build/libs“ ще намерите всички изходни файлове. Тези, които са по-важните са: + +- j2s-common-0.10.9-beta.jar - Общите за интерпретатора и компилатора компоненти +- j2s-runtime-0.10.9-beta.jar - Интерпретатора +- j2s-compilation-0.10.9-beta.jar - Компилатора на JavaScript, виж [@допълнение] +- j2s-lib-0.10.9-beta.jar - Стандартните библиотеки и интеграцията на Babel, виж [@допълнение] +- j2s-repl-0.10.9-beta.jar - Простият команден ред за интерпретатора, виж [@допълнение] +- j2s-repl-0.10.9-beta-all.jar - Комбиниран „Fat Jar“ на всички гореизброени библиотеки + +Ако не искате да компилирате проекта или нямате възможност да изтеглите нужните инструменти, на приложената флашка, в директорията executables“, има всички гореизброени файлове, както и Java 17 среда за изпълнение, за да можете да изпълните проекта. Изпълнимите файлове са налични също и на уеб страницата . + +## Използване на проекта като зависимост в друг проект + +За проекта е налично и Maven хранилище, достъпно чрез URL адреса [https://git.topcheto.eu/api/packages/topchetoeu/maven], в което присъстват всички компонети на проекта. + +---- + +### Maven + +Първо е нужно да бъде конфигурирано хранилището. За целта, добавете следните редове в „pom.xml“ файлът Ви: + +```xml + + + topchetoeu + https://git.topcheto.eu/api/packages/topchetoeu/maven + + +``` + +След това, в `` вмъкнете следния код: + +```xml + + me.topchetoeu.j2s + [нужната библиотека] + 0.10 + +``` + +Като на мястото на „[нужната библиотека]“ въведете името на нужната Ви библиотека. + +### Gradle + +Първо е нужно да бъде конфигурирано хранилището. За целта, добавете следните редове в „build.gradle(.kts)“ файлът Ви: + +```kotlin +repositories { + mavenCentral(); + maven { + url = uri("https://git.topcheto.eu/api/packages/topchetoeu/maven"); + } +} +``` + +След това, в `dependencies` блока вмъкнете следния код: + +```kotlin +dependencies { + ... + implementation("me.topchetoeu.j2s:[нужната библиотека]:0.10"); +} +``` + +Като на мястото на „[нужната библиотека]“ въведете името на нужната Ви библиотека. + +## Изпълнение на командния ред + +Изпълнението на командния ред става чрез изпълнението на „jar“ файла „j2s-repl-0.10.9-beta-all.jar“. Това може да стане чрез следната команда: + +```sh +java -jar build/libs/j2s-repl-0.10.9-beta-all.jar +``` + +При изпълнението на файла ще се отпечата следното съобщение: + +``` +Loaded babel! +Running j2s v0.10.9-beta by TopchetoEU +``` + +В този момент командния ред очаква да бъде написан JavaScript код. За жалост още не се поддържат команди на повече от един ред, но може да използвате командата `dofile("име на файла")`, за да изпълните даден файл. + +Освен добре известните стандартни библиотеки са налични и следните допълнителни глобални функции: + +- print - по-кратък еквивалент на `console.log` +- exit - прекратява командния ред +- measure - приема функция като аргумент и печата на стандартния изход колко милисекунди е отнело изпълнението на функцията + +Понеже командния ред е с вграден Babel транспилатор може да се ползват по-нови конструкции, както е показано на фиг. [@babel-usage] + +:::{.figure id=babel-usage} +```txt +Loaded babel! +Running j2s v0.10.9-beta by TopchetoEU + +$ class A { a = 10; b = 5; printme() { print(`{ a = ${this.a}, b = ${this.b} }`);} } +undefined +$ A +function A(...) +$ const test = new A(); +undefined +$ test +{ a: 10i, b: 5i } +$ test.printme() +{ a = 10, b = 5 } +undefined +$ function* myGen(b, e, s = 1) { for (let i = b; i < e; i += 10) yield i; } +undefined +$ for (const el of myGen(0, 30, 10)) print(el); +0i +10i +20i +undefined +``` +Употреба на по-нов синтаксиси, с помощта на Babel +::: + +---- + +## Внедряване на интерпретатора в проект + +Основната цел на този проект е да служи като внедрим интерпретатор на JavaScript в други проекти. За да бъде внедрен в проекта Ви е нужно да добавите зависимости към `compiler`, `runtime`, `common` и `libs` (за инструкции използвайте ръководството по-горе). + +### Създаване на среда за изпълнение + +За да бъде изпълняван какъвто и да е JavaScript код е нужно да създадете средата за изпълнение. За да създадете средата, изпълнете следния код: + +```java +// Събитийният цикъл, който ще използва средата +var engine = new Engine(); +// Средата, която ще се използва от интерпретатора +var env = new Environment(); +// Пуска събитийния цикъл в отделна нишка +var thread = engine.start(); + +// Регистрира събитийния цикъл +env.add(EventLoop.KEY, engine); +// Добавя стандартната библиотека в средата +StdLib.addGlobals(env); +// Регистрира нормалния JS компилатор +env.add(Compiler.KEY, Compilers.jsCompiler()); +``` + +Ако искате да регистрирате транспилатори, може да използвате следния код (имайте в предвид, че тези функции създават нова среда и нова стандартна библиотека за всеки транспилатор, за да бъдат изолирани от останалия код.): + +```java +env.add(Compiler.KEY, Compilers.typescriptCompiler(Compilers.jsCompiler(), env)); +// или +env.add(Compiler.KEY, Compilers.babelCompiler(Compilers.jsCompiler(), env)); +// а може и да бъдат комбинирани +env.add(Compiler.KEY, Compilers.chainTranspilers( + Compilers.jsCompiler(), env, + Compilers::babelCompiler, + Compilers::coffeescriptCompiler +)); +``` + +Накрая, за да се изпълни код, той трябва да бъде само добавен към събитийния цикъл: + +```java +engine.pushMsg(false, env, new Filename("test", "test"), "console.log(10 + 5, \"this is from the JS Code\", Math.pow(10, 5))"); +// 15 this is from the JS Code 10000i +``` + +### Работа с JavaScript обекти + +Работата с JavaScript обекти е лесна, трябва да бъдат използвани само описаните в [@имплементация-на-javascript-стойностите] методи: + +```java +ArrayValue doInterestingStuff(Environment env, Value input) { + var obj = new ObjectValue(); + obj.defineOwnField(env, "test", NumberValue.of(10)); + obj.defineOwnField(env, "a", StringValue.of("fsadf")); + obj.setPrototype(env, (ObjectValue)input.getMember(env, "prototype")); + + var arr = new ArrayValue(); + arr.set(env, 0, obj); + arr.set(env, 1, new SymbolValue("mySymbol")); + + return arr; +} +``` + +Често ще Ви се налага да създавате и Java функцкии (трябва да правите такива функции с повишено внимание, понеже те имат неограничен достъп до Java средата на изпълнение, което може да означава че зле написана Java функция може да компрометира сигурността). Следния код е пример как се създава Java функция: + +```java +var func = new NativeFunction("myFunc", args -> { + var a = args.get(0); + var b = args.get(1); + + return new ArrayValue(a, b); +}); +``` + +# Заключение {.nonum} + +В настоящата дипломна работа е демонстриран интерпретатора на JavaScript, написан на Java. За да бъде създаден, беше направено проучване на съществуващите технологии, както и методите на интерпретиране. Беше отделено и голямо внимание на добрата и проста архитектура на проекта. Архитектурата се развиваше заедно с кода, докато достигне до днешната си форма, като не стана заплетена и ненужно сложна. Интерпретатора беше тестван най-вече ръчно чрез сравнение с държанието на V8 при същия код, както и чрез трите транспилатора „Babel“, „TypeScript“ и „CoffeeScript“. + +В крайната си форма, проекта може да изпълни всеки JavaScript 5.1 код правилно и със задоволителна производителност (зарежда Babel и CoffeeScript за около 5 секунди, като повечето от това време се отделя за компилиране на кода) и е относително лесен за внедряване във вече съществуващ проект. + +Въз основа на всичко, казано дотук, считам, че целите на дипломната работа са изпълнени, а с описаното в [@допълнение] - преизпълнени. + +В бъдеще може да се създадат няколко класически оптимизации, като например: + +- Кеширане на достъпа на полета и използване на „фигури“ +- Динамично оптимизиране на междинният код +- Цялостни оптимизации на алгоритмите + +# Допълнение + +В тази глава са описани другите два основни проекта, които вървят „ръка с ръка“ с интерпретатора, а това са именно: + +- „compiling“ - компилатор на JavaScript към междинния език +- „lib“ - стандартната библиотека на JavaScript, както и няколко популярни транспилатора + +Описанията няма да бъдат толкова подробни и изчерпателни, предвид опасенията ми да не надхвърля разумните граници за обема на дипломната работа. + +## Компилатора + +Няма да се отдели особено голямо време на компилатора, понеже е изключително прост, не особено ефективен и генерира неефективен код. На кратко, компилатора се състои от две основни части: четеца на кода към синтактично дърво и компилатора на това дърво към междинния език. + +### Четец + +За прочитането на кода се използва рекурсивно четене, където се дефинират отделни методи, които четат различни конструкции. Например за `throw` конструкцията би се дефинирал следния четец: + +```java +// Пропускат се празните места преди символите, които ни интересуват +var n = Parsing.skipEmpty(src, i); +var loc = src.loc(i + n); + +// Ако не се намери "throw", четеца сигнализира, че не е разпознал конструкцията +if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); +n += 5; + +// Рекурсивно се обръщаме към четеца на израз +var val = JavaScript.parseExpression(src, i + n, 0); +// Ако не е разчетен израза, се хвърля грешка, понеже сме сигурни, че след throw следва стойност +if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a throw value"); +n += val.n; + +// Шаблонен код за разчитане на края на израза +var end = JavaScript.parseStatementEnd(src, i + n); +if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ThrowNode(loc, val.result), n); +} +else return end.chainError(src.loc(i + n), "Expected end of statement"); +``` + +От своя страна пък `parseExpression`, въпреки че е по-сложен четец, евентуално може да разчете функция, която в тялото си отново да съдържа `throw` инструкция. + +Един от недостатъците на имплементацията на четеца е, че не се използва токенизиране, което го прави значително по-бавен. Така беше направен четеца, за да бъде написан по-бързо. + +### Преводач на дървото + +Преводача на дървото не е нищо друго освен по-сложно двойно обхождане на дървото, което се получава след прочита. На първото обхождане се инициализират функционалните области на променливи, като се започва от най-плитките функции и се приключва в най-дълбоките функции. Следващото обхождане компилира самият код. При него, компилацията започва от най-дълбоките функции към най-плитките. Това се прави, за да бъдат правилно уловени и разграничени локалните от уловимите променливи. + +Всеки клас за елемент от синтактичното дърво съответно има следните три основни функции: + +- `compile` - Добавя съответставщите на елемента инструкции в подадения `CompileResult` +- `compileFunctions` - Използва се като служебна функция, която да позволява компилирането първо на по-дълбоките функции +- `resolve` - Дефинира в подадения `CompileResult` променливите, които елемента декларира + +Целият компилатор се базира на основната функция `JavaScript.compile`, която взима входния код и връща инстанция от класа „CompileResult“, която съдържа служебните данни по време на компилацията, картите с местоположения за всяка функция, както и самите компилирани функции. + +### Четец на JSON + +Понеже за имплементацията на JavaScript стандартните библиотеки е нужен и JSON четец, основната логика за четене беше отделена в класа `Parsing`. За JSON четенето беше създаден отделния клас „JSON“, който е отговорен за разчитането на JSON текст, както и превръщането на JSON обект в текст. + +---- + +## Стандартна библиотека и транспилатори + +Компонента „lib“ се състои от целия код, нужен за създаването на съвместима със уеб стандартите среда за изпълнение на код. Тя включва стандартни библиотеки, транспилаторите „Babel“, „CoffeeScript“ и TypeScript“, както и дефиниции за буферни масиви (`UInt8Array`, `Int32Array` и т.н.). + +Създадените стандартни библиотеки покриват базовите глобални обекти [@refs-mdn-globals]. Имплементацията не е пълна, но покрива нужните функции за изпълнението на включените транспилатори. Библиотеките са написани на TypeScript, но се компилират до съвместим с интерпретатора JavaScript чрез „Rollup“, като стандартната библиотека, заедно с потребителския код, се изпълнява чрез интерпретатора. + +Включените транспилатори „Babel“, „CoffeeScript“ и TypeScript“ отново се пакетират чрез „Rollup“, като само е създаден код, който да свързва транспилаторите с интерпретатора, както и код, който да разчита картите (Source Maps) на кода. Интерпретаторите могат да бъдат използвани чрез предоставените методи в `Compilers`. + +Създадената система за добавяне на интерпретатори от своя страна е създадена такава, че може няколко интерпретатора да бъдат „наредени“ един след друг. Това позволява изключителна гъвкавост, както и допълнителна изолация на средата - може код, който използва компилатор да дефинира собствен компилатор без да знае за компилатора, на който е базиран. + +Освен кода, за изпълнението на TypeScript кода са включени и типизации на стандартните билиотеки, описващи специфичната стандартна библиотека, дефинирана тук. + +За да има достъп кода на стандартната библиотека до някои вътрешни функции на интерпретатора, беше създаден и класа „Primordials“, който създава набор от функции, които могат да бъдат използвани за ***незащитен*** достъп до иначе недостъпните чрез синтаксис функционалности. + +---- + +## Дебъгер + +Това е втората част от „lib“ компонента. Той използва `DebugHandler` механизма, за да прихване различните събития на изпълнението на кода. Специалното на дебъгера е, че имплементира V8 протокола за дебъгване [@refs-v8-debug-protocol], което означава, че дебъгването на програма, изпълнявана в този интерпретатор, е възможно в инструментите за разработчици на Chrome, както и във Visual Studio Code. + +![Пример за дебъгване на програма от интерпретатора](./img/debugging-example.png "debug") + +# Използвани термини и чуждици {.nonum} + +1. Дебъгер - инструмент, чрез който може да бъде спряно изпълнението на кода и да бъде изследвано състоянието на програмата +2. Компилатор - програма, която превръща програмен код в изпълним файл +3. Интерпретатор - програма, която разчита програмен код и извършва действията, описани в него +4. Среда на изпълнение - съвкупността от конфигурации и системи, от които изпълнявания код може да се възползва +5. Изключение - специален механизъм за сигнализиране на грешка по време на изпълнението на програмата, който присъства в повечето модерни езици +6. Инструкция - единица от данни, която дадена програма може да разпознае и изпълни по даден дефиниран начин +7. Междинен език - език, който не се използва за писането на програми, а към него се компилира език от по-високо ниво, като междинния език вече директно се изпълнява +8. TypeScript - транспилиран към JavaScript език, разработен от Microsoft, който „надгражда“ синтаксиса на JavaScript, като добавя силно типизиране и няколко нови конструкции +9. CoffeeScript - транспилиран към JavaScript език, който дефинира по-различен и компактен синтаксис от JavaScript +10. JavaScript - по-популярното име на езикът, дефиниран от Ecma 262 стандарта + +# Използвана литература {.nonum} + +1. V8 Виртуална машина за изпълнение на JavaScript код - {id=refs-v8} +2. Пазарен дял на различните браузъри - {id=refs-market-share} +3. NodeJS - {id=refs-nodejs} +4. Deno - {id=refs-deno} +5. JavaScriptCore - {id=refs-jsc} +6. QuickJS - {id=refs-quickjs} +7. Производителност на QuickJS, сравнена с други интерпретатори - {id=refs-quickjs-benchmark} +8. Rhino - {id=refs-rhino} +9. Кратка история на Rhino - {id=refs-rhino-history} +10. Nashorn - {id=refs-nashorn} +11. GraalJs - {id=refs-graaljs} +12. Truffle - {id=refs-truffle} +13. TC39 комитет - {id=refs-tc39} +14. MDN Уеб документация - {id=refs-mdn} +14. Еcma 262 - {id=refs-ecma-262} +15. Компилатори - {id=refs-compilers} +16. Междинни езици/формати - {id=refs-il} +17. Компилация в реално време - {id=refs-jit} +18. Gradle - {id=refs-gradle} +19. Статия на Gradle за миграция към Kotlin DSL - {id=refs-groovy-old} +20. Играта „Minecraft“ - {id=refs-minecraft} +21. Програмният език „Java“ - {id=refs-java} +22. Алгоритъм за стриктно сравнение на JavaScript стойности - {id=refs-strict-equals} +23. Алгоритъм за „хлабаво“ сравнение на JavaScript стойности - {id=refs-loose-equals} +24. `new.target`, т.е., оригиналният конструктор в JavaScript - {id=refs-new-target} +25. Типово преобразувание - {id=refs-type-coercion} +26. V8 дебъг протокола - {id=refs-v8-debug-protocol} +27. Глобални променливи в JavaScript - {id=refs-mdn-globals} + +# Съдържание {.nonum} diff --git a/doc/text/img/debugging-example.png b/doc/text/img/debugging-example.png new file mode 100644 index 0000000000000000000000000000000000000000..0811052d4c69d1ab92e06a243cee892a338a151b GIT binary patch literal 98441 zcmb5VV|1j?7d0A9Y&(<8#1l`zBk|-Sq*#GzW`rt9BCttxNgnucyWu9%gt0@`3c3)Tbjn|CV zz>9wktqt9Vz*n+3P_Rgov}iOdHMS@%Z0t0C=(OlKu2!wApZzf>H8KB_bw;67SX#^O zSDXl1;PRt>8u;5gI0emX)h6Kdrq$>$^Qh%${7>5J`jmUN3NdlEDykr?{Tee9lg$Nc zzjZ-Qj@%@*y%;SORY6TnPD>wZRR8j_@W&u4nz19y*Fe-fdt+l`R#(_vwr{$jDvTL5DoJ=foFzW>-)R0h>3L2D6Hf+ZEMlV5wb+X?`8qwZU2y+)NFpCF3+*Lu3Y zRvVTe?95%{xcwz-CMC&vq`i)gqu3lX^*rv1Q}5wsF%Vd4M1TP!#f{i=d|`9*hdvN> zH+gm&YX%NbWhih8yD0O?&OP^sKH+IMi?0NkVXsiupNS*pu!{s~NEsaMROjIRoI2;q z=N4Y3t-SN78MoLv)c?wQ$qCF8DvW=oQXd&@hZ0S7Dy1tYnA42+wRP;*d{ z1SVfdq`Mrza;r1304)&)XZ+~8Rm-9fG8c;S`~;^;*6A7c=-cAMHc~#7l!8|uo01%2 zaKxW}1o#^flR&<2ibm2204#p=#|aAwk%*E8Hg{kN+3S4w^hNwOa*RfCXEv!<0rdE5 zz3O@y817UnG_~=zQ*t4kAy&Ne_aG9U$lbJ)oXd{=S>%9yHV@7%#)bENro?$a`#E`7 z*F{kcVmo*)aX^ijWN76ZUSWo3XV9miozDnF?HEnZAdzl?_m)qpW3#Uc8BQNm^ zzg1@7vXm6})YZ{tfNwlxvVUd)Jb!#5^W}#2=A-ntPPZ3hyPWO7<-KQydOx z{~cLeA(T+|)(Y$d7N@|2Q8K7jFrvD1VM6LHZQ!T|A!i}4y58=!zSVyB&x%N{d1xjs z_7r1r^a3l37k}`oi!AIC)&p}YZmPYjyIcV%HO6Ex_*5eObwDg>f4gR)-pYxdCP}Gr z^88K{qZ8@9qn~N<6uuXrQsXv*j?JLaJA1CL$DTSe7qVyRoW^UZQseTfYox(pP+WG2 zh5_2o^2j){fpOCi;kkp4lRa}d=v^d3Gvt?XYP@$5V`emsb6vsRd>Z`pQ-)2PbmJY@KUL2u%YpT@GeG>KY zEue=$z(oG7{mccq*Ri-DAO1*MUl^4nvYq+VN9}h^DbLBCKH99O%OQKK5k&gJ*IhOX z!XnkzF!jN(!m(kt>!R~e2{STm)K=Ei-CnKmY65)gfQ(`m{3sTiDrU3<(6I|v*4HpG zG5@vjoCGg97TyHQ?M+OCzv}4@f$znWnX^x3Dsu`Z3DL^&%Yd;fdm-21c3`UgZPQ%~ zk+(OSuBSi6Z*T?6GblPWRxesOS!a~BAP~)qG0{!?n7E493-~QN#~TgzClTBgHHj)3 zUK}MXQk0k~MDRL~6KngYIF76TH&QVUbcetiC`dJR{Xz(Mr|)9HHKuD#KT_)gv+Na+ zumnRjlY5a~FS4k3I{mx$Sg2-(;@r1Q9Y%ds>hZ$!B{5T6tti|JTsy1zsP zz-vGG+}wk4IeeQ`*qtSaD96#q`rv@#i0!n+JC?x1JQ|4mSmUa-@-Q^?H4K?dHL3b` z@|P1)UEWA1boWKJo}=-H8-QGL({yAq62B83WZCjeXmHa{hanYBrqAb@vCmukC(M)a zE@GyPYR5>%Klf#Av?w<0xDv&Mz&dS+InvCooRoj9s|(J7-FjZ#D&-<@j&;IPLce-$ zuZawN^esK8ma}p=-}cLZxAwa+mrD_5U7hbjjc{|?_xoLq2haW`P$_RoF!|jcKxjNqti;01jbrgSQsju%nK8+|I6%f7?xiK&hAA|uA z`;;K3Y&7Rx8PASZgH3BM_l4UAl*`WcF7r^4AveSMz5DwyBycH_H+v&;tnZSb_y;JTcSJC%*1OS!ula=gav=U3H<=r#jM zneUqVX(_FN335XQ$|+QbUocwCezD?+*~+bD=*~8w5;>u0hcFD1O@WcO>=S27j;ECa z0zGOUe^7YO61G$F38Klx6aStcyx;$v3uQ|D9$)XmlTs%A)p6!%%p35}$%zdlU>#%| z_6?Q0owOP$1-;g!Nj>lk+{83tD68#c3&7{@)8(ACzLqt?6PljLT31=zfBvgxMHveJn7fQ=wL3m~uk^%}NhIRaGnd-NAs}cVqB-v`rScuLAFh>rI@lfw z39zfkPA*Ev@z=v-0!(k(mpGFdN@?Keut@Ml;qkp%TLdTZ1pAP3Zb-W&A=AXvUT0v& zKkZ00rwF=7Vfd%YJrC`#mz1q|O8Z8Ae_&iRLH%rgc_i!3chdNll!ema@v>Vmv%NHd zfkh3)@agwV;?upm!IWdyyR-zX;3a?H^Zv_i^rK@9{Fz#dK7~cY6UFE$wg-`zIkksZ zyPBZ3oS99?u;`beeoT2ec{=gl^V1wk+QYmaG!IYe3`)I7(4Pnbr_AaQ{2YbI)Weh5iT#*10mSQlG^)_l7pV;T^mDRRmKfpr# zY)aj-4&-1&G+}!?`MIcY|YLNqWSiH zYkjRR=l{@|Mv2FBa=W9|#}>S3CtJeEivTvm%4DHMyd0Q;AZG$+YNv``DD1a!FekX~ zA$UYeAvi9)U}W(N)TyiJswXGsuXiV%1L|kGbxC|Ak)+#caCh(sy+_ZbpFg(pPM9;z zwRcxIU1?o(XokbK1?<#GUSJiVTX#>^up)kfA@TK5>=;hRrJ;^4lB8f{LdqCxuiWEK zCKyXX<3`gNPW7bv>i8=HB*xDaANqh;Jde^;Fl5@tkJDxz=1RECdvF__;kJ#EWHf=O z4W=@WXj9v^uJ@)m!Ln!YU*ykTY#4wIco3Ql9yk7cyLkg^PMM9_dE>%2lf`wt-S@8LE?8WOl} z@o=?pM90Q*WLp*=4FmC0{QoT$zWKq61{4*Jj3TPc7sm^;|cEcBB<2<=2A2=jy-?%zl* zpyES8c*4xZYcy~O5#_3^O(>W`RS{C z9K+f3BegYBPUv038ULD;gn#FEzbnJ_2ZQ=ON*(iL{_|7*4o8wUbEw)Q&84lZdHV-w zQA*WcuC_N0wVn*w;S^A@nsTCvxY^7+kEQ^&(+{9>@>a+TG*;Bs3-6DHxr8CpNXqV# zmc7!#{*50#44CD9*W<*8tLrnMq4qIbP6Y(C=JT{fK&ISH5JjOnV`Dg${#vznwqE*r z{%?g;kA&yPXCZ`C*phItmDMx^RaA(6$G~$#WM}j*Z)Pu--uDBL49>$Z;iO@vw>gb? zFrc4zjS58pxINXD$ya?6u(+SlhMtW>lUhNfyusfzRcqdsaGk=K{>HN!;roT7%Lmc+ z9kma@wnbEAHO2CVHK4TcNDuW$rC;ItOq#^veaaF-3mi;GPy$F~NEj%84ygANeuAL$ z1L`GDY8nG?a7;cqzEeRU%=N_47DjrS$Xn14<_#yT#{-s3^F8R@JtMeh^?tx+PuT%? z7*)xZC1@LpnjZEB1b7;v0V8x}3dsiKK&NGQQhvx&n8;mk)XIc+7FjuHDr4v4DPx@2 zmm%5NY{Pt5sR-##*w^bDEn7(L1#_ypBD5Zi+I`w+#?)OeJzXkE=f-gL4wIJ5G&mQ1 zHpg(ZGp*X&^CKa(UEvZ-{&8jLuMi8*RW6OZTKnEoXCerC*}btqJVV%;mhtTBE>quh zqK?5Sqly@KrEDHfOPYX=`9SN>WM$1yGBS$bee|!Sk>7mZ`pc$LYRDL&>*xdUUP~;U z#lk}XL{Zg>(61?UM#rrifdc~=3=$=%d4+S0M?It{21Fex9r`P{r3)ns7Lhg$^0faB zTS)?!%KwGhjDM^;mueY<0Lvar+Sxzw_F3@YDyy-3dK6anJs48crIvpMqnHmI@!_Vh zPJ(g$G$0uB!RwQbdrG$pkWkF-{ra?RRQRh9?Dse$3p5Qqg^r?K;DPOD#=D9{pmvFK z_tGh*GTx4_}DP9yE#)T4G#)ar z1)#0_dBS_W@3W%`^nC68ATdr+vj%_XH6Il>bUWjpPMeXlw<}|xVV~3%lBlBApO{XB z>K|OyBHv<U35HQk z{}P94rRTfDX99l_`i+oGL1dpzQVE>vh2_HgEi6L<(aJL( z%4bsY;+{hmR5N#NM;eF`28p|U&oP%hYX_~miUR|*aHP;^fXB3`AoM<7Yg0Y`(NcL1 zgkT2umZ9M4E?onI#u>u6HAU6ul7twH9H8WmtjYgXlF5U2mfN$McX*BPZ!SRQM3Kc} zqxHV)bitoA$x)19m+?kfw4QH#mVB*6E#dq8aG2lF#lS!X>p8b;p=C;y(>?GT?UU*U z2f9qFF$~x1V{tVA%l*h-t;LHa&OO$32XjXC%>EKx!1hz7-X=-tw-Qyc%#i$gBW+fz z%RqHUcqnAZNNueUNoR_oZweUacayZal{Oabw%6@E>2E(d;xlOl!r+H49%tpzgzWSf zXId6QY-dXOA-yW5IXq@sB+cKl5_jJ2UiAeHBkhit19ZGE`lQpkfh1i9(suB{@tD1n zMY;ADftMYR4zApa@XQ8q+&<}!$A@knuDw&$7!T@Vu;Frs)lo;?Bys!TT!e*_!*}M>c7mi4w?>bT*Dt9>=_cRwj?ph3$^!`Un;HOCrj)!RhL|9qf1bCqveLw|L1vy8!}1upHtdLwyt?Uvl>$m+7O+x z7-NYSWT8N7*3|H_@eKq7Kx;>n+rJ0&tl_x%Huf-R05_sdUh3zIFTSodn*Z@{%!x8lnPJ9XFaLJu<%x3Vl?Bvk`dk`Gq3c!lOtzK0=j^z<8 zY3xIZv6*T(dF?RfoWau>?fNjlXsV0o=<^=+x~4iKRqUE}=LjoAY*hkT4)vt~=JIe! zu>}|2A#A-l#gNPN;v%pB`Z?87jzPu|FK98m%fV1F3b-;`5(55{_RUgB51nq%>L!q8)hJ6hyb zb{1Qa*FxqO^>C2J5vmror+;=A**87X^&F3tP{DRnONF>6%+}2NO0OXj{yi5&)WrS| z4`ml3$WVk4uA&JmRH0i=z2Z)rejQlhx1(yo+m+|Y0jlP5H^;p3@S=+8)UEdxi<6AT)aC(OVJSK9=@r-?As+`72^?uPCIuy-~2wSRubZ^$!SZ3(R&g z0o9!E>CIRy&)gjqaK)uh(zyOW2m!It*5a_})?C6Or~G?A^JXU(p~9bXZab#*7i!WE z3+=_CKo!~3*RQrx7k#`Jhb8x%{lQs>#ma5VE*Bd2Q-__u+L^A2-!i3Uiid8G9{I0E zCXIf7V>4aub#)(+m6#tzQBQqEt)p}r+JOFe=~{WSR7G1A8w6e~$e1nsrpgxA^UdMN zZcyYiv|eB6(-7#Yva>6d*r23RKr!qulKrd2ronbUWcu_L5x{P>7rnIKa(B7}sY!=f zAdFUb$?{MEUy--tC3DU>w7mp#GoPd761+d>;^ymtD^jx~?t}HZ5QsK%tY@nl>6L{c zxE{XGkOHzGhsH+1m13q^&BJlcMKXd5%aIx}Z7I+}5N=j1k9^SN#~GZr2deu?^X-Ky zG680e`t(OBfP&=$2I4uB*+olsG}GTh;WS)Q1D|PI-5&qUj8kM^n)n0{WrnRMDSF5`xK^6{r6+&v`sAxV ztTdVbRF;Ew`MF)CQhRE^Tju{_ZH3nwMW?p?C=dInTodMUB`A6)W3p7SnHkN{-(Sg) z^&UxGc9_T-c%k>4KIG={>YZrz0chrcn0p)Fu4y)J3hH zqGfwdf5~r$iiSBqFi6xC zJJ2w07*(rgYGM>CpNUUO64B9V0RlyT&s$8N-bzd{2m%1(y8Q1h8=V(1n*p9yj0tSo zWJKayZCRHu9aH6y+}8s$b6VwlWi|GS!25yoH$2nmmwC?FUCWR7C^|_-(O)VmbkT}L zRMJu4!&4JQ;=1A&E!Kp5stLRi;;VH@_IXqxigVXuC89T62U6Ul5OaT$;x4F`=6kF2sTmG(4xZ z`6TJWD;iAC{Y%N>+UFhmoaYQihQL|YMx)P>fXRl&#zCuUQL9hE-uF9RkI(k=LdCbY zt;cVV4IzIoO*CvE$81`|5; zV5&!07WL! zHb2M1Qk0a(4P=q`15VrteS7Pf^yay(&M#WlSb*-)vmwGc^X>Vo z`cp6F!+X-Wc7Er6lB`Z&Zk){W*_oAtqvH&Y3M09VJ*@)S&$9?<&+^|@WFWwWKq=&C zYY=`q-4ls__KP4Y{H?j}?Ad}AhJM}v8V2Nj8;U@&zvP>urb_P*!0-ZBDQy@T1C|wG zhdip5w{m?|N*Ig52m_vm#=8`Qq@G5bo)p=ZCMHR`Rb+`d1YAM?5kPQ@A+=&YBKGKw0SZvSL)!a!LCSo zOyAfm5@d8@jo~rLkhu|WYPhIrSrrp!!zjs`X#t0#c}x&Opm;uYhD`d@%O2%!`U(r3n47dZ=;=g1bz z*#?wmL4$o8LaRUb+S{HzF^cv03DvIK|E}XJRY}Nr&IolB60GO3K?%(6-4~8reHa`S z4TuXO&=5P-==TWRuk)}Xb^}^{+N|GSeVu3eQ&6PZszbBI1s*Y_gCELmqMC}}9q12t<2YJc&KnVZ|*lwI(ncA-x z+Gh(5KGu8Jj$|BJn~>LxMi%+Z+u3^~i+z+7^eswIjJ9yzao>AAOr(RyF#x~|T}cT# zXeq#c8UTCS#~I4Go9mDVTm)v*U?-D=iY#NiMqDb}Gv$bq+feX}J1hSGs|8?7!$G+@ z^5xqWhIgtrFM-rV5FJ>y#QM&@{{*<6vWtsgLWG?!X=m@x;oca6v@hU=g5e+f3<{Fw!>UA;o?NEQK>D1dbOvJ(^PO z%8ko*r&av>7D88T#YiF3nFj*pOmxj;>A$zDZLeV53*q!#IF1~GXC}42NGxbCNt4?> zB9>_wv@$bM((AdEQbKys*Ru4&E{S=}EC@=(pFCvp34suOGZAL6CgX8eje!B1cZ?>m z>!GQCQzLxAtF_<%8DsAG0L{=Ora^!0{39KtL`cHU3@x*>dZdzdckNjm_5;UC#ztKm z?!Xsxxrv%%6zyOR*c@olLgbq)?aBfUCxrWhqLG>S!p!>Z>*EO`kvR!Y^U3{V=gd+y zkP}fKlNM(6cJEYmRY3OY&x=@@9*rs{1_T*Kxm3MQYYkJHoI>+;wqDe`yHeaw?Hedb zW{%4L$2o!q&r8RSrktKpXul*x=Kt4uUP+~!0VJHKByfd_1Z@>_j7B!y*VhotmDoPxPxSY0ogAgW& zf}01Z>mMeFdWp)vv$uevLBV->1eyORNGJ(mTFNjpX|tFbBcg+cMc_0QsD zH29fi7@a1-K$GKi?)bIr3jZJfqQu{7b8D_}pAYcezdD&h5A)>ml~lXZP?3IF6L%+O zBcvG=mzqlAd+~1W&4FjEtXje@BB5`bi}0y{{``}+Zm}_S1YZ51#O}uHPm5uv(R%@F z1s-?EjOzx+vyP(N-Z?8^Ni*bTXXjoxNy~HHqBVYv%3kdTwc@dwpjUT zg;ng*qJ5LA`wS41_@Q}EU!_cx5M9k^|4tA6XpxO)D^099xTb2j@dDYQd2?PF9)0&= zL}CcDVAVRhCefEQhu?m29DHvbJI+3E*}~_|`pHEsV*AM%SvFN&GC-wsyx+ZIh=E7c zo)rllarH0*!N&#a|2i;|@S5Zf_9Hqz>?8kP&SdklXMlfmFQ~fsYPHH4s+euD9ag!< zD}9MqPji@Au=-R-_9Z!&Fq=EHFT&~8WVX_Y39X5tmEG|h`Vd!h2rTY{e#&B0QxA}#;=w@ab>@emvFg3Y` zN+5$0_x{=7O+)p~X)Ik`F-!=s)Bb!eh&^#*F`tcpmdSQs^w_*op>dCA%eMy8^n(3j zW3_n;o~`>v46yl#718CHlKUKN>z-BWI>M6}dt#gn25tZb3#+2Ez~7UE$(WgA$$e-S zYkitPeD%6~WG45H!vUG+ZbSxmBDT=ND%NXz-H#Yq(rT!i*}Qa8c%*lp5QBu~P?9== zreYO*n8=3vuGjkQX~IP!3qQZ~-mNuQFJH}$bVi`FaE5P0QZwdZ>iR*@DKGDKH3M7> zgQwOCKCLH$#@hjvCPT0cfee6cMA-DF5A>Gak&_QBtuArj#j7ZzLx04Z?Gwl3`Pz7} zv?oWJ(iG-M9*b<(9Ld_dW$ODR`S=U(P)1!gspX=B&u1IBLD#Rj_v}+n0$^Aj)o=K-_~?*DCUlNsGYZjl{piQC?cf^_9afUwv+gd%E%srtz!4T+v5uPJN)f zNs3E6k#Ciese{hSZ89Qmdnizb#sIl%!r`5<=*xOkIbPsJMz4P;zKRM~CyyRVE~VfJ z>#w-Hdx2FXAN;jO#AGdc>m zW9Af9Y~GM0Gyyis&3n_=+*TEQf;11*KGms0M|BYz=QX9yH?I8`YmLXksWHW?^I76b z4cUuA)Jd!N_?-76YKTM>I+H<6U#WQusw+1t4(gVQS; zXoVL%J8OL8c9O7Pz(D8Y*$7VMwJSGgO-}3FYQ{o=vu>9TBA~j;y?C%}sT>i1E~atu zkc!npoNMY}87hx#V#k)@#j7^e^OB|m#%cNnk_QSCYRdF@xBHZKOy%S5V~J&bBcGR| z^)^svNfv|Jzr9ZLc{{+4u;>G(X><^)ti$c%^N>jxV|;fh*^aS1`Tgf8XC>vuG;}iI zYPYJ6H|}%4xQXw_&oJL}liQ*RDD9H9ex9NZh$}j-zFOxQBZ5Q@*P3p*U2(w2!&_(L zy08M>O{!A<6_{_4jIqBVr8uAZ>$~Kv1dE!XrT_Q5r2kUa!ne~N+dHY!`#VaE<^gSn zOh6ok3~$&3h0^|>oB; zl$$OsijS_2`y2&$L(%wuRA^Rc>zX`!LYg^h8|ZKUtz{bDTJke3;ES!4g{j4IeB0a_ z;AOYgi(V>NS+}lDZb)E{OSg?q+7f90`IeA~_0E#Fl9PY{7OLIdGiCB z$?oAc^-^be^h+_71$W|*4qz;&0BD6IL%UYOnnlu*QF*KT^$LAC`Z+qxP!!GMuOhmn z-@_Ek$%2m7M;FdAYCM=LZ%+xzQ0(iN7cyWHTo5*WGvjM9wbWZbV0whX?q_A5-hBNz zf8v<#)Xvyvp#k@Y<6!drTtTUs2}hUpkN0XJ<+Zfd?M0b@!4iy%%2Myy1=J%aET^4c z6=^vA!E76DR!(Sk`=MzFSNlZ0sIAxv9La0ZUe2O)n zm`YQlC0mZotk-J}jm|HwmCWqS&LcubyLFkjixvKtW5ZS<)~T0>_)djkclR~NleAPi zGn6Ljcj9OUKRg0Fd^#VV^wxM+=W68WEosFR5=Ojn*vX|Fvtw0tu!e?uo)XQ-)EFfL!)5D{oVk30 zqz12zw6Zgi$mtmgr#&MSq`(D@Nmw2UmI#`$yiw%;J%gl_o7isiic6DLej{5<-_r+IQKinkQbW0JR5@; z-rer0wzZf9NmYXcf7#n#Pv{m@{RL+|QE^66i7E|12yfiG_V$))S<3Mr)38{p2r@RW z5E&4Wi@7XLYRq4XOO&e%F6XhiPaZtJ?CQ2&l$26`hYHK>$y|A4$t6812Y{9SDz&$B z6gi%WqqZE<@mwCpC96;EZXjJdfNa}wE&aPLk1icN(-QJ6MF?Gv3n?#OlQb;Jm`s^^ z%SC>mt4=wKa-*nfmmg)T#!u89oqIEDeDo^4Nl_86*ne4Z;1CTS(#IjPg0#@M&iiWe+qEE?!2;pi?$@A*}@ zw54;S%Q}-yOpOf<3YbsK8kk;7zrvI_G+`4g}t9|N(OKhj zfAf3o+A~eL4n|KqfT2{GxCvSg^PV?D=L5}p$j5DCDW%QSdt&2t%itg!e|&Il8kqSM zNdEZ@ym%k1HaLCRl=8ZF)T;Zl`xCuslGM&OXwUYvvg~$$y|pQ=^L%g%oA-Ex-Fj8; zNK+aAgr`iNTNHo&C1(FPSE#wonM({(^>(%QYhJZZn?kw#>3vePXz809W?WWQ%gd1BaNFzyD`>91cN!sj4shtV=ldz5f-2Aj@~z{U>S- zuKe?R8LNVllGEI@W}!luO8LRf!I(6LuyoRQRKOQLwrGJBsaDZw4s%lSjtAFKkQ6&E zNqHnnBj|`DgPqux0A^UcLa4^$Cc&_0gP_uZePzLz8Qx4;#l1va#s!&kLVSm4|?`tP0wI7NKpxKLA%2ogE5}6?S!-5oSQVQ z;gtVg0TJ=kzx+ik?<75jnrpVD>zn*oFz{aPK1zx4cc)4jm+T#$ME}ASMVAM_mNhlC zICnB1zrk>SqC12;uFaJ+!8Q15O1NcF2N9PmoD_0BREH;xT#VKyri_aiD1Xh^$RCr9 zVx(_|rY97FTvQOG)B2HpiBFjD@|k@)Lq4FaAhnR-)Y53IV)~|G8QS6XP$aQt@$j*t zS2b&2+Cs@^*7LsmiSbuZ;oDxRz4EV(|RcAQiqp(19%u_)bl)=9(9A$dcAYy3}y;%B9 z;}j}9{2o&&(X3z~R^tll;_=ndM3!gNAAIzc1G!YiN2-%=NlR1(>3w>MH|U#!TD zgzrq*etycxBr7x*as?9qUyLX>osWdd=i@qbvAop0Tj|t9@)n;Y(;dK&klsnS64Fx! zlu}QkpcGGOr#v0_vL~i%t}>N^H%!RASmv$Z2lP4}F(TiWqXIoUBKNP*LKi^); zba52tZ*TlC{XbUPFG!{AT8QEsZ`55a?UROpGeL9lZ}-zFRvcaD8V8~zeKT{VcXx9b z4n^@_i0)ruUs@^iv)}#kri2bhs#mT6%7xy>R)(iMtdNw^`tzm?x$}kbZfvnR5UlK5 zOsZk=sL>yoy(QPWEPE}rLr!qQ*6;j{AE;i|^RBj@O@lww~?2-GSQ;*WBWDF)PisAA3usXFDhpfVy`%I<&y-`fsuemYV z6I0F|lf^bsjT~7{X{=2O{}`-lr7m?xyPeVm1{=0q zBK8n4N^=EMt6U-@2sLkj>PO-jFzI+Y7ZQhlUVk@{J8;#3kmT`1dVU7aD}9~W1ZWk8 zsquP1SrL8Vl$~bhAt?G8d>YKMoG+~PrYnHsPVwYg*sQ9=)v0%IVcI#AB zZ5tdx$_^}=%8*Rk0gR1wtgY0VqfjblaE#d64;9cX-d%L2s!FlnzT}?wFWytQp3ix$ zRFjx~dc@c&ppPWo*V-zqD73rM1OD*vuZ9mZ;9L(bqda#_aJtFj%Umu_5T*X3o$Bw@ z%iWKvA)7s8hEzcmibgF_ryuoaw!bBTVUV&Cp+v|*aITFp6JbZhwwcb?;NCfTj$oulX5zMg+f1+HhH?dzOH;~ zju+Joo`RC<_z_AVY6wey2DN}p11>`6E}JW;t*ue4Tt0v=WPDjMbLEZtR}T(BK?Jrs z8f!yQR8V%ocRZ0R6i$jf2)Z|7d918VX?4seWB}fU!L(nKTjML93=xNR&x!EC3bWa^ zmFVggqc01z!ld^gw-{}+ewUQbzP(RW`$OIF#%6Q!+eG8CM?2y_(MKq^vQ1*gT`t{%@ykC;n(&2jVnD)n|rou!91_5Vy_6j*%SKD{n*KX<$VOmgw|HwL|ydcWF*c4xDA5$THV(3CV6Y1&d0q=mcpPi5rmYut8 z{6=|Xpn6ea!LVDa8v9ouftT5ih8J0xLWbaqfL0O^uPm>t^aFdQ6=mW}jk_M^+@PBM z*<8{YVuC#s=oP{ddUGl9@c~rK2pQbyr1&WkuD>ht9 zzGJRA0Llqk3%=4Tb9X541|Pcoox0sS=V;lcb;<6s2eSN|g`Wr^$Cy#>=q`LwWOWBq z?bN~@%Y47D6L?!{jR{3@PHRxF_|K@0SpmCko(|;aib|GUdCCD_sD1$GcXpUm6Z62QSNgp2Lst`J9;_}h-0+s0&V>ZxIj$EF0#Sy#9tUd zes#n*m3=f{eO`oz4;k-H?4_^=iSTSI{qy2UjH2R(<;MH0(=bqPme-*3kX;4&p_i@tJoj1_$8dw%tOU-|I-ri$); zbaS)b+5hIs*hOd&*hxPn*0QG#Nc-V*jV zNsY}aP5$K*V=cX6Z5Rt74VX95`tQ&sb=WbOYZ8)UWBaJ&=?DwjUHv7)1 zC4>RhM_dsv914@!f1VCoU6sQ6{D$0Z>Pf8Wkc`B#^wx?s;hx*BJ${7p4<6jA8?ha+ zzMfIhhmd^ZZGj86DHcJ)v5|H7_ONjBMu4037L+$cnfr87*Q(4c3BUcDuoC?{92T7{ zh2$8cckT3N1>2Eo%=&=N%2 zWa{M6-wkoT0Zj38a$Dt{iTn5A&YNrQ46#XIwo)jr&(#>FtSIg}-LgfGD`rffJ zQg5*kL2Kf-ZbPGi`#-&m{GT!w=A;8}o)u4AZz4i@tU^?@mau~mlsE;2#y@+GUN^%+ z%LG2AJw-xp58i+NM7yLOobBp@Vq`@H8o7v;!%T|;fyEtIYg_zg+VuJ zTZ|-ka$GiC&fqR^!OeLgN3Kl&i2@=RB5jTH6{H_nWRwA0gX4C8RtH$36ufn(n{JX7 z#l?4pQ(xn4#~hRbmN7GU-pu!nK%- zeACDFB{lc5#w1(rH*6xk(CTac7)&C!q#(ye``ZdK3+VQ)I8wB^w9b!1i-NW01F!ws zY^GcP66WxB?fUQxY5U@AT#?Ia<8}dg=1TbU<~-J5tF(Q|`XB!b5ZDg0psxx5bcdrn z3g3`{kJBO!7S{t^OjJp~2?+_srKAcqM*iRm3V?SV zjQ+r>%AHFi@p(_xO4b7)wB@P*ze42^rKbs$!TdL+S_lu)$InL#H)u=1%vpJ!I29-->{D*JiZ3pZ7u3^ z@8MaUsV=>+$^2iMh!UH^$x&TfXivT@(a@J*qPrHO^=ah;do{gMaBUa1lGW=gp_3=uf z?j}8CE-Jm&ZQozb#e?ccr=SzUSvG%fk(@{1noJb+m!J;lR_BOY<=F+5eEC09Xy8CE zb}c)ahCO(Vuz@=}`K3-#pH+>aRe!k&SS+XOUf4Z48a_Yo5e3&#xLZQaLQDDc3zOCl zRk_h|@gBXy&@A*ST2t+jUE%(b*~g#m1Q9giZxt04a#WZlQ48!|*9?K^ziI27jdvnN zEu&US?w6BgW3f0f(-(3%B@r;;zkqZVMked23QJN4)i`&h65mH483b2{> z1|-!d*wcja-k%hEBz5T^ZFsl{5i|yK0xrK+r9&7i4^$K^c*&Ns#B@_mw3Q=W)OSE$Z9-mIY)-WL4R_ zd9vEHuFE>s4bFkNQ%}mMPmc9MFl8y-v7mHZtJgT}%39z1fn3!l&1LXPQ~s}^ZR&h=dCS(76& z#t~h$2lZM*CrXW`&Z90|<`1kBqgo2~L0XJjsQtgnuMS65qjh)6% z@c@n-rx>{ap4qSN*?Ww}n9611m^de|Qs0IoBbAgkqcu@{huh`64JM-AL_~EXBGS3z zYX9)zdv^v|c@&!$wnPo;Pm!{&j4rV2pq%pUw4rxTCkSl8D?~Z7Jz4b{h@8KA^*9({ zT+*+L&5wt;U8NZd_j^rn13m-|gn(K(fFko%1Ch#B8F$i^12Ftinr?+;5e<{~?~1z6 zD%H$ngKC@quNI(|rH+A_ge|a4z%?_vpN;Tzz=t2+b<%8HFaEm{NA@CS+doiCXNcR2 z)FfrB=SP6w04;QBA9-qg==LVT<@9F;dfAFCMErx0JUUoS*4EV3ox}!@JNm0B5%H#~ zu3!KjAv`ZMC1rfgO7-Ro% z3-RV_x?L6fJS@7Xj4B5I&EPKtY440=B0w_5c6=i^XF{i}QW5p;SyC&YD4Nzbbr5OF z6W(`C7mlKhGRbkUt+ZG>7i1>O{9k%+4~yL4N*Je?Vr!H`mHZ*zD*Ey*DfMA>U#_06 zEiQ?GY(}~@>FDx>5+qi?hWk7?hx~t7d&{6Y`makA2@oVea3=(JcMII zUp%jh;cqXz{2qCU_|54zY0Q5jP4}#yaRa{zc_G$ZD*NlOrq5uu2PN02($E+Whac1y zBB8L-1=P%rb4(i}x|F%4|EVP&)*p4-W~(}P4<)59<9Rgx#T()EI+L_G+q1D$FxJpzT)9J>Lb7k& z`P?dy;^L;S(h8gY`Ks`jIB_(wpz&|`-y}R%Bd!0UYG9R6@|fOPvnCNx8~^4xAblsM zN9529NuCYX4uf1Kxplay`Q&Z^agB}Ly@h+g=w6lVRmpSNB|(ve-Bp$4ki)+**F!Rg z?`8Jsy)r}SiPdW;ENybZSwhg4PtL$aE9B%=l_VPsYE<6(%=i3-RUK|q?7dS?tDd|P z*;+yr^eEV2A}7v?0K`R$i-ic)`3>R$Z#ILbadSEDq)6;NBE~ZaSGKizt^tjI(!G4g zxpDLReSj7~rjn%CY0KqCcT$aVy~U#R=AC67f713p2|R}LO^eMR-2!|4<%d%z;wW+r z20mIuefiPBza!s~;J>W88uXH_yni%pD7hWPmtK_=aXf+>NPfGyCX_JSoCjrjr#x}|_J8@aU>#?WtgzP$ z{a*7n?^KcFk%QW`nOJ!6iMCxaMP5#OCGp=={|!6+Z)nF?Jg%?fQl6=UDBZ+&lpANu zY0O?tz?Q?L^U?~ynxsvSV#a#r+EKEFnyGR{d((aV;4#4#G?e*U$BEd&h0XYi#Xp=K z^k>51NVsnQW~u6$k*#h^TIJXrB}N%fr_moNw){zC%n0}nU^IS9+*DlQlKs2*!PKd* zbDqxhML*zws#QBw^G_05=@RPL44Iz3X?nGRc(&LIpDK~?YO^%zbV6zHdJksxq4&f_ zO?6FfM=`vAD3m7w8r>X51(2qa+#DoeHH}{b zr~=Hm6V1P`+{|b{Zg?FRNE2^AHEZNhk^YP)1^Fv-tj(`*nWF9e-M4m2`qD5bAMP3R zM!t9U2vEN8PHatUki6UWleQ%UB=`9|8Zu_3ypV=nbc)}i5FdwBqwUo>c1qI`F^vu!e^|Mxk>K(6Lf z_dZ$-cfC4NYvAzbT za)a+niK0d)(AwLk@Z^tQruP=^Wb&By8x3Gym8wRz=CJoNc*G*}6qcwDT>`sns+&vJ z>La;t?Ib%QM_Y;QB`~nqo25={@?evpY{2RSHwz>VVGLmO=4&f_3eP2W)Ch-g;y@8+574jbq4mv{T18?s}r>dop zS`j}vK#xlX>!pF_u|sl?hYmEL@Xt&~RHuA)A_%eHm+>Z(^}?Jwy=a|wIrob}p^ADJ zA2^&;nlYoaAfFlgxlr%KZ?{+$>22Xj^mN z=xX)rlNrYZ4(-zB?sD<3^`QkAo4v<6lRl<1CjvJ^#NW014bH?0k9y-1vEyEf z72)1T))+DkRoD3+gQh`yA9zGuvAbkB`wDICHxQ(F=lLang!H_Jv+22kGoY}Q{;LZK zb`=5`R^q1Z1->VTbWl)nR1Wd_D3$G6N6vm++X#<$CaGk z*1ETEq$XjWz0#QV0>Q_caQB4OuJFT;G~8K3bWbOGPsQ~o7N@^FJI51q9) zk3He6beQXFM8aGNxV_vpkX26QWBv4UOpCA4_4(%tHW{_+|(2;8JpSp~8 zL)?YUQQSHfS5;hlt2b%ENDznz0)(1E_E(isy_Gg3sp8?{OMqhfpx#^%nQkOXP;S4U z3Cp_Zi$aalSM0$OOoTduXPnv0FK^d4jA*Obg+t=SbUIYa)oE?|le?>%v3`)OVF;@E z(Wvz}u~1omLen7Pbag4R+?7?z#qz5ZRWu~y$00;uhBueTjI}IXv3TT*m6emI7?PzA z`PDst)zsqVB#K729)1S0BM9bdO9Zxuypwp3!$$$-FIy6NUL@j!f?zD_Q$j=GMn+Tl z99CI0wOKT)lod5p{l*mqH!6^pmIu_#wam-B-$me{H8`3?FtX5cYX6l+?Q&9fX9$c9 zV99ENg$6Hg#`3A%XRlT$D{@ny-rwK$TbX-)#qjU`p{x74D8nU*7l{G?8##@28O@j_ zhJ^xR+D)K8dk!#YWu6q;y|s&!#$EaF!m6V3e;@9bodI^bh7c6J5@c>LSjCa=<>St_+! zvRtoTp&n0~w=qMTQV`1FT;?>&Go9nESUjCRy~j_mgwtyh>4zf9?*4Yr5HQv z!b26AWn_M)M64{{ zA25}YMVF(|<4AF~qYsZ~7Bkm2Ps>z`zzX>--A}3M=n9&ent+fe(M^wjGjgS|Q(@+H znNO8&?gF!t04XU+O4{O7d*yrug{*$#AD<*i zIh@jk9xX+8_Rf4;I9&`A96;Rb^qtbG`$VGQ4R7Sm|1%z5Fd)(Q>8X|d-6-*ExYq&i zfGn1;?qwvP@$>H}*zSZ>$z?Y^vSi8;>0Y-VpWY!`x6b(evZlHYU_4Y~^{#$T+<)g_ zK~`J*34M2MB_bvm+^~hbDF+JYX_|>cA*_d5-?NwgIQzp^9(f>ti)?`k>%) zK_R0iLC$K#<<$6cDC?%Uz*q&pmin$Pu|Ve^-P(k&M^^o5?TK;dm-2JryyoZ2V#Rol z)LuX!vgVhiuo%jvH#|xSC($v*&Yh==LRjFanxj&@;l$dHn;Rp^6s9VZD6+%b{G$sG z|L)9=C|>TAv&+oAI~lnh){*p>BS%~ z)dx<36BY7!4QZG*kgFAi>Nh=-R_Nw=nZGD+I}JiRxv8Y+G=hAL?zIU2s1=qFUAC$% z%~>+C`?Mg%lP0#M+t%>tlhUq4&lkeDJ5X4f46}eeusobg8S2Wxdl=SgIXDps<2szo z7GCz7=>C<62PBV=HxYOC{7XG#9i6cdQDHb#X?8}oyoCr}3(b=ny3&6q+LUA?=s)eC zYv*c@h{=hur43n7x$)Qh8BuGkDca~6tk8ZPY2+C)U3;z6`{M)%JktJ$DuS>kSA2sl zV<%7@!Wo?!^YzyAoMguEo51!Wv%$i80L8+s5~PG#BJRyHS0@Cm>6C?Xnbpzv6mSH1 zD)N_InV0~$ZLohcDguzx3ye}Rm5?&h@h*g~-Oyi%!o9AkMUka?=#j3b>8G6zvQ$Uc z4~A)BcyW$%fA3Rd^4&8eIBMjHe-``pw~7Kf+#?=zxN!7{@+G6a$H3g@b``HQ6SlrZec-D*#C^Nc25NFUd;W_(q4Ge_knTSXSHl8 z`CmJ&jN~(l&1DQB;comkupHD;T93#5U<1<$`Jyza^Ea6fIgvUaBOl|83}7m@BfnO7 zQ0pN8rD^6mza|wj(<4Q=&oEf|B6cevSI~AOX>%w_8XyD$>2tZ45I|lgtS?y$C7gBd zj0qWMNk(B~`e-7?qKEVB;O>{54I4W^G-6opM_AC)sLIL- zAwUhqZBQwXr3!@>Ec;^zxM=F|QeOPl)}H4jIFlxjT5bv)b)PwlD07AX@vT=+W03X5 zm`3XfTqL4@E%^**!zY_sk9u;;Lx)cD^4lU3*uui6}KMrMZ+9$d&Zh~uS&TENGvfo z%OVmb(YF7fy%U&T9nr)98~s8j5DWUpNPw+`Vc_#~^cs#IA}NVj5^~}B-wN|{n>77E z^;Ju^SmTcfi?(DD0nHS+U8q;8f-57MV+&9wQ9?k;Ri-EE?FH=M#}p=wCwH}O*qW8` z{b#der64i#zWlyy5Jed^>>U_fnu<}H*J(b;;7fZup;n~o?kpDqMT>yv2R%KPK54=@ z-X*(J6Nlb{+DeF{!}EmY9c8t>N9x$r&rS-bqIT*3D=44)&iCH@yba2l8YOC;TF_AV zY`@&}mk^zLk47NyG=v~UE&oXr`%JVFwXSI?-dm3O4k^ij61(mO4hyp%^4b@8hsTSi zhOZ8fG8CpOs;SMS0@n1u0p1o5VRQe#0q^m(L`Cf2M}BY8Z&u$VOQUl#Gxp|wLvmD|Y;-mT0J|L|j$s*R_6Il!ZDIZQ!#b?kr>_PSCHs9l`}LP*{35;D)q(SU6``b;{h zMlY=?sNg0=0*9z8Np?P_I$>?bV=@J5UGL-F?!@1M1L>^=frqGSIioDIAk$w(4z*B_ zOZTKD-K1OzU!Jqd_RJw6&+a>MC7qPTViBk?Ai2-XfHqm?j}^wvjRFF9Pp_}B@$rG< z3vRgtktkw^133bi)d9d>_#yR2ju32&IRT}sg*3}-f*Y1>)H|aJ_u$;*tjsI4_XC=~ zmuo4sG<;pPFa17K1SlQPFl1rmI=~lJf$iMk4`aRi-Ht|7hj;lARbJtp5Qwz9Y%FM) z+@ccBPBT_9J*^0bzSD37W{0O;IW6s!Q#3BrMq5n#yVTtx@(zJa?#;oECuLc3_Ub#Q zA%D-Cw|b|MmGZNXD_PwQ3v+$tFI8p6_v4lt5!}6ctAkDMU)GL0f5PVKBz$#KBXL6~ zz!IJKHuq(sL&)m%(sSUv)FcZgmnK>kG3EuxW2YN)p|lnM>k+(aq6S*E3vg8k?xO#ku~Dn}y7V|~UOOU=Q}i07AQg9a8p6k`Ugp;1dm z*vU~kM6oa^q0bX3R-sHd&rIrHgD2pr5c4y*O{9925ugcuw$|KK>SaqycHYwGm=gdu zkH~%>EfAS+{%CmXBF<-`nlb)O9!(&8ZU+G57F88>v-u=!uS6&x2qw~X$XpI8$~v3O zj`)6ohJ*`*h)|tHgjJyn>h$#Io8%exFTEh6W(yTcRYk!>l{1alWPkDGwcXhq1y0CA zWzA(xcUf-g!T)mlXkB|gVWW{bE|iInZL(J4zNhWW-<}Z{PM^!F%J`t1A+mY{o)~yv z9*x3*N&~$=K77|Hq}N28gFXn6{%w^F z?LADnXf3Q18JOLK0ygH!@Dg)Mp zm5Y0OhlQ(UBE%lA5$t)#BR!vy1U?hdk&pZmxN{lZ>Z+cRZSEL1DOEEmQ7oQ1z0WHw z)U&te(4;{tP}INerR_p@z`=qllk8OngWe?gA$ZTI@{J3rC|IU^6KFs+OTk^^S0hZ! zbHk(a@)G;%vG8n-wsmJJW9!u~GM;o)h8IxFuO2zSS(i8Mz0-Mc3?)N7PD^(YEU{=Z zg0fJcHLjbo^0Hw$$=nR63YQ zNG?WYn^i$;q*lx=hbL)j$<*QOF8=p#sa=u-BUki)q07<5C zN-CEVcFd3M>OBJHUC-WF*VEnM@Q}`@5L4D%mPZ4tXU@1|n6Dfhf65SC?nYQ{Y9v#f zYeqj0@Hw-ey`>P_i&_y7WAl-H(z>u2neY%8Lo+Ve<{|Y1`c~(uLta0Qk&nqHC`_j9AM?v8%(cu=5%S(nW07BCG=_$gl z!g(w+dOgCW zy3-yU$USC*{?m9XlR#dUC$bCA$2%1(q`@pzFj><_vptcNkREnwMiAN-A>c#Sd+X|h zVz>3-YIMz)qSR=*+?nW)9*g}lNEa!ILRBFz>-BQy)Kp#y!jg6PwadQ0aV?cjuC8Yl zVv^NtBUQzY*IU`iWz&xGO1X9Mw$!VGsr_HSxSeGd=&wMyoM2Ke=KObh&_EM?{)4D- z|LW8Xe;4z3xcqgi3-p7ppFZ<&=OgTwBBQF1j^zH^OWL_HVUJE+p2ltXOoSKxzc7V$ z01q?@nl~@8$+}Fi0WUwN14>EV)r=VD*fnIFiW#co7q(kmG{3`;h_^HfiJ~Tl;b0KV z*FL~=7M=s=7&*$!?E{ZQ4{H)^QvSN*=6p{MswKzm0h2QW86M8j!@ng2oJXI%lD16X zDG$!}PeumS96YoLhEz`#>*7Wf5@x6b8exKx5G(!%myrY2cts3TFR-4ywvVJNsM|r% z@?CL_izaZnMzNx4ht;Q)_FvWjs{f<^hq!<^diMX9Z9MmRZKo}sjU)z$2+5!qM-n8P zjlg={(u%Q+Ydm5ly~AaG#Z(vY{m%z-gu-Jh;)qUrWNg6gv1UrN9Ul{8X;~QECi6I; z{u5d*6H(F?EHUiOf^UA(9i+k;hiYGDX;CXQ;QlT;DR;4kd0xBIa58$V z!omJSDuR8{#(JvDuv|yx!L2SEPnz`GzKvghThdK#pHQ?S~cqulFfK zZ`@z3G#;1-Og1>RX1}{xiI9NzX6$bAMbwWN-bOYq+j6eIEorx+WGS$Jw5ckfAg|#! zQhlQc_|?v7#5E(6IFqP=D#(DZ4$wsj1#K%0Zkeo&J$CN>Xk;jm!4sJ(9w*6M&#B@` zUVn9Gq~;0lI!n)Ey?f;Yq#Rwc#dvN27C^V@gG=fs)@<6DQZBjTL5>RS9l^H(^uGNi zP=~jBe3x7?<(@f6tChF`phqTh*I#pL+96X6_9$PG>dm?>LLDM${Z6l1s-iEuJaAJ^ zX&u9ox#@7->T+9~?}Xv{ z#oP~~F2f(;h1C!zHn&fcdyJ0LU4G5})es4F;u&jBS-uL3Y;^#GZv#8}N7*=bp@WDdK zs}J$v$UJQ0jj8-y>$O-EiCz`pa!tmm1BD zT&`wrxy*&9-_WrydZ`n3uMt@^lzAoKlgYP=T`g|=A18a_>|$4$2;c0fy1K-q(06>a zx#c&5-?|!}U?Dg~t)e_11m&Mqv3Qp-F$55#cp~-_LSJ?u9IvArYq~q-D8$Ptg#6w% z9L(iziB-`$jdRi6v%l;{#lA=xOS#;k=t*T{E@U>5hqE*YE8>X$@e<4c?HFNfD(a-U zi?N1u8Dr!O)WAOz6{qJPk?l*9Qk~?R=|>eYr}RcGME$z;dqi_+xCweht29)cFM0B6 z>nB2xA!W&|oIe^)o#xe%3vKP-%T`5|Qk;F#`gG6-&92oKfGUc;sm~P7XB;lD&Kk5F zx`tIprM*Hx3s0~vkxd>RHPj?B1h1sT>M90lzhoT_Ely?j8jlOrh9<*);gy&)i^Q|_ zpA=Oihr1|E{!0YlZm6MoEQO^tobEqSljFJkdMZhGQW)dj^&D3d99td9-%=Es(pQEP!r2IXRGPl> z9BV>TBi3I31op3nQTwyT>);wRJFi$>;Kzw&$OPTYVR-Y+%}M?f&!|1ZWjC))Jy3sn z)gLW?z|R!_>!G8DGBk?4oOtmR=U~#+KgxuDZ-bBb=m8cGtB(VKHo*Jr(kXDY`M01KSAsBWp?pBd_!#PpMYWye!q0^%b$Mt z=6Ku+rQ{LtI4V%+Ul7EW&YjPmMISOLdUTBozmrDh2_iwXw6GxmghGeIZ-FMTkXPXo zYqTXhiE@-IS0)d&H%CSeuB-|1Vi7pFxO_f4-7gEHC4NdVM>w5b0bwXqwn&G5n_*3H9c47MP zp?9Mom<=R3!^G|t{1}1mdOK zq`h$H0TU;>Cgo%XRyQPMq?d@0u5nBFPW+)fLENq@B46cvk?7~cS7E6u)<)Fr*s%c> zZVAhQp%fj$p(|g$q|h93s+!#Ub1`~3c#$BBFYnHW{!KIkA+Cv=sPI#$(9O z*O7)k75H@>cPAyeB;^ll%LxM&U5y{YqNQs~Rrd_fZ0vrbNL`gQ=WPyVjYPA_%#uQD zll)bkoVU95>#&(z2r6%rRy;F9s%#?{(rF&}0?Vr)zy}z~)A>xmpA1U6dp6f%vSvJ0 z*4A~b;HpY0#T+Tor=;x5I|`h@{-=u=&{XCm|J|JpmbV|%wwx?GnOb4bggl1xN_C?Z z0IjOU#Q)Y7hwuFeaszdnfz_)={F{i+sv|i~KYCvpXJ^b`L2(W91%0DlySyxD~Z?xq25`S)Nx_<2b;8_FD z8t{bkEq8*4763P`U?Yv^gYObP^NTZcFNtF3grp3NuEYCCl4;odoRhzHyx*!dTq#9Rb^A=Fn3x=UVZnDuon+@dkd2_9oiMcE3Cwg(f+y-g|&pPH~NV zOkz(YZS~%q>r(^X;vBKbfoSwWIrziJ$;>R@;!GYc#J<{R;aM(>R23fks8 zv$xXH;Iuq#l?D3uc#=eA9HEAi%PR(mjAR@OX&bG338!XRasVn6QQn1aMTJra0>RnW zB*h1!-W-eY2QF5aosE&d0>lhf?jSu@4A8f@5PEI(TjY33y`@^V+V)k8m&F_>L#^|~ zpKV$hQt|Cw6+(ze!tHy7j!dR}%j0_Pgdh6xzSTqWle<4&jEA6-b6xl$68!Cbf$k7= zdguJ#Pgp}zVI)vau;Q8m!}Wgw;JD@3csf*~v7{7_=EF|_aS?+lneI)Wq71U6n7C0^ zo%f}LezBvAHP#EEz!gD$p31)x6MRMc(_nc91jnjDy7k~D$=(wD#ZS`TUYf>#E5PojiN#8P34ia0Z@kV|3m@+)(rX zAaC`)!ixZsT3hV!39D>4++;?6WGBJjT@}h3xdi|(es*~?JI>f16YQ_n?2(+`Pq6Te z?U{<|Hl2@#ZnU37d#p*h?u7(fI0J;3hgzy4FfxzEknztBG_!=w4q@g)rFig?!O zTRt*8)M>^r{!4h~x zqdh^FUWG@*2aq@h{o^T#y--&q(L}dk^yqs>%i;hi^X6;va!K%NgvH4q&C8pfqnd7$ z&ai;Xg}0teUydinr}`bg_EA@xiio4eH|+cHj5<|EP(on6&KW!_=*V((%shh_?0e+h z-Hx-qN5$H&E@NZ{S&>tpa4HE)s=}q@Zrmu02P!0X3nmvbr1SEF=D3&_xpCUzY zWZ~uH9PaNN#tQPA(Mk0VUR0r_dg-PmhNvbcm2`vIgvFq!dvzEV}0@Yr>mE|N1Xct1gX4elZYOv++W|*xu@5qlKFE2Z$$v{dlc4p zS7xh2|5-)+iER(z!w-G_X$WBe6J36XwIF0#!hPHKYSG9a_1DDi&7 ziK=d4=;>f8lg8tfvEuxE zCDfJoD)W}{v66ED9{0iiBleborJ+f&npq3A0sgaJ={GVyG1~ z7NmRjVk<$R!y%Tc&-lk-iGPivBv4zdUshj$NYWq4CLJ3qOR@N9qn(>d<#0mRK8}Nn z9RqcBVSVtNaB?>~NlE>bw}R{~B}(J^5wmGJ%VaArO2giVBpP5_C6E))*br`(0}2I?IzZ4L_KI>)XN}c>KiFCeA4R)}+UtkGywVp$y6`r{uH3+k)RR&@ zybt{;zDS|E$(%we=PkD1=filK@JAGfnxUZB4ykTub?ua55}Bz-Fn9wNV7bVHA{fl| zUo{J;Sy=PwMZ5E{dTQ{L1@EvG6iq+Th#q2V;?Yi>iGzSp+2Cw&I9pF1`kRC+yn(!K z*DOI;N%R%Ow@~GL#raoAc@2$_ z&Q6~o(tmYT4)&@!J|4eT`L)JczEfXw6Kp*g-zGh-!$q?H;b?Adovyc3bL3ioMxrRa zva|2ecJ^i0>jqcc*`cJS%Vr`D^;(U}I$=Z>N{=EzLPC0bdy_B0c`hpvH(VWrZ~oAu zroemV$n?C!V~3izb?)|tW2m|>%CAr&CggPocn)#s?p*vU` z&dOg{y}yrAm&*G@QXD-2l5PgBe9Y#3OTaHAGzFqw4X5%OT1`m1yklLYFhmg9%n#?( z+aYb9X-f&p5EAH^+UYG}xtoENeK=wEiQc|&UvGb*S+jiGE90` z5}&iN=GG6cGlvb2a(tlxFosYx=FvROr#~P!n;IAc*}?go9Z+sln<;`YHnUN&2~Pr{ zvr7ENn$m^I(N!LES*F-MzxTE-hLgt?UJqWF#aJ+jS|5E``xz`6bVq z*a5)geAuDQ%boTOll0;AGUpOIYX?hf;f%EQWbNMnH&WOB93moS!!Y$U52@I4CfW*{=Hy!#D@TQ3Y_%_M9?I>Yt?bNH~3r+h-@*;F*99>3mL!T^5~0yLzX zo12<6NGm3##f*hOfdA@D?~)iyKp&V>oo>77k}ezHJ0+MsByc7ozT?H)cpK)#Zc$Z_^g#Mmkw@7XUkv z7o%~|b62%`&4V*syBb^k9&I=tyf<8abwR?a=lQ`Il)@au|C7L08s9(r!d~n9=37-9 ze#96(IH|y{t%(sk=9&(x`oOr!L-6d`J84Q?fl=N?b~t_+2{7nVq693TDDLX3uPbiY zP%*@2v=a7id{1Sm!EhthHNF!wyDWhyFh%{Qv`bq#e3&^d1acneQ@FlHlwRPMK5yH9JZgk{c5OXMaq^)t6v>A&u`r z{RpC$EbpIT$-}Ou6~JI|ohZ#jZ~UB_8KYt)>a^>CF>bwuLrMxVI?)`6+Cu->9Xwhk z-0e>FkbhcjZL6(s(~Tv&S6YP%4fNza|5IsorMv65SWOYUc`kVNj{Y4%|X^ck7GM`>Z~;G)LutctMokT`5i9Gl~QW zg_T-Q;EhW?l-)((r-w-@requoLqunsPwsrZ8=^DMPvMEh>p`(B#>{;eMwdGNd)$Qk zGD$LDuyQdrBg4DDf7`6Ou6}1nRCT7lbSX2>NVX`M?&!q!H3wVb()$lk)CtAC+Y(L3 zwO)78x7PJ-dKKGZbubiD*Tl*5ourQPJ#9BqF6xK4o`kN@hie?0=tHgHT5*0J>7s(_ z_G!+R_$>wzD9APJq$`<2HQaJ@JB&Dc%*TidT&BrE@Jx|UPKBB3?CVC`t_+aZ7+T1p z!0D)tiy={7+mfWVsRplpVYjXRHWV(#zV3s2y{%mV{Xk)D?WXqTQx4mQK=C5-cayaV z9f0jmgCdvl#4Nfdvvc*adK&^NnIwf=PHz52I1VLoZ~shX%)+_jDGeFrIy`AbmUMAya|1}IFp<(ytE~jC&eWby;+LB)yn})WwPwsv+!Y9vK6kQ0 zv~S?|29PJAAPz*F7F%T%v(<$s>!fScmIr305;*h3Njh*DN=72@)GTbf+^YU4S75i! zc6v28*yST#IIEfNSznKl zcmP3h{V<~tlew7_kxea2j_kz+E^qJ|N2sq)0%20K7q|mQMHS!FT(ouO>Q{?Mt6DSp zYE;tWtWoA^+Ukl*6kw=i#_;LE^e8y{Zovqpmgx7Gb7mer9j#82{eb0L{poV+?~JyO zE-Q-FiP2Gh@{TY3lVdRJ8ZTlPa(q7xdOMx+=Dnu~#v>aOc5Xiss_fQ1lud+dFKb1F z$G!ZBP}do4%+1QGJNA+;T@R6nr^z*wel1kxivn66QP(;xzD=NuhHvtY$Z6qFsLiNr zfPDM6^g6*-M(_(^)9z~1{W)3c1-+mmusRG`j)nWi69-Zh!%e>yE{D@J5)xs7E^8Fn z?x1I^evgH0zqPGOs1%nPR9tdUx?18XTM&>+s=OQmAfs!^p3G3})TI5h71#L6=-_v= zo!PrPBKs8@tH*Z6m z?Ujl^EYyao8+EdR(H$wK=>`Xl#s|IyoidI1=npR!J?hH*pZl#F2%0gIF9_E;vI*Q0}^4t5m{FabJaiQDT?}`2o*1K#i~(gK+D1 zowal&@KSn-By}k$w)Bt%J#SL%(Ny7vgZfw; z`dAy|>XcRUXwcAdSdnb@-SuHV2 z)haK}7(kqQlKOjmA%c%CM&oWsS#bmPYwS1%83QwewaQ6tFz@VS&3PM6SQ@2MPu&&stm<-feq z!_Ap-Ijjfqo5Q+WVZvu7QxX#kYWwbOf_!Dc583cPhn_+Lpq7_j-;waBDsGzqV8mo{ zMrJOMl7O$pK@HXCKiSHcZ{&j?Hr(C=g1(etG^c^DC1b&)@9K;qPHGZmRE1N@h-MUx^&HBeWEaMNJ=%<< zeA%(hR5V~Up$zHMEK{OlAC!xXf7HE!dOV~yLdk6Jnq-L;MzgRW-bp&<{p-DQZMuX` z`8OR_o5RW;S-I6KJk90?G(IdytNiaMINaLaWWgPubqNDwACrk=rOpsBA86<#ac!^Q zHp!_^FIn}aSJ+E1>mjbv_=I)H<$DuXq_0VQl3MS()$DE`nS}Dnts!Iqz*|ga;}LPs zJNaZw65~@vHm_#i@7z=8`oIP&a3BsXl`)5p7CRC{Y>gw;=;A&bFWNYQNx6HJzzZNq zF(H!7L5BI|NPOf1!P^1El+U;P9{keE6V{GX+EU0$M1BNu`e=_p$fQ&rE{h%CA z{O^$sc@D({wB;1k-6o7m2~`Q3s9c;yVOI>UAj)3Epz@PxzCwkod4-xOeBCRnL9`daDLvCd=08YVUmR6kEc z|D(H@c8l{yIJe#2df_?KrNw?R$N!f*HAXOCA?)q%>oER3(FD*)El!sU$M$vkIZn1U zfFT1DuiB`b+-VXkz2S~~g*eWuIx`m+7f?Eab8EZocesQ$tD4lAATcLGw$?8vhuMd< zxWl|(dfz-xd6iwwa8pwVT9A9XIX%DQ?9Ij!t;6UysD9A5v)bdTzBZHYh~DG*b_Nlu zwni`kuv;S-!S3$i^?&^EDJj`PFa@C=yh+9w%_(OOqrfLB#5w*0=Y zK&Vy~y_;iJJDJxv)GsvN&#yFUmM@ghE~##oC|`>de^!G5Htro-dDq7%uTFK+MO9i~{f`7;L@6~T-fbz`D&H4rGY=xJ0+4l@!s}Vg z$fH?-Xsdiy)Q2cv@xkwditu}$vtx*B~?acV_e8y@s^?5u2TGfPXers>dZ?XLPlfN>dd?YUaYu;pA{#l=iqPwGwNM%HFXKH@#d;v?d;gPY?AOO21*A@aELr~#RtyU88 zf|4xw@j3HflkhTODW*zTpDf?xDd+HLxC4e)9`q;V&Ij{A54TSNJJ+s7HYM9;`;2P@ z^r{~mgvMxUG|iD+sABrcJTF~8A#{w-*4zA1$A8iJ+tNhjKCR1ftUw_D7#JTFXKEW1 z6KkOhCkwd39s%$>d(@iKeCsqosa5%s6i)?VNXBTH7OJF&=GJadF?)O$B0Q+J1GX8` ze`?QyYu+ICmEW35;9m5tH>=@JP)t)K=IOmKS-vqcjo#VNp+DAjJpSn1IO&8N#Dd93 z%_7@ku9XxS;(vKde=hKXF*{eA@?WfV%@zW@=1iy$sMJ2x9#%y}H}a0`O?ft04PUN_ zC-yu|b2+YDO9#jNT_u=Ak?-yapmgns!uRUt`ecdgScbWDl17?JrJMvj*55;0pBo51 zAQD&mpA>eJ*xadB;awoAT3zXvDQh{_)#t6vDm05tR2?wf_Jb&Gi%5R#POeo0%o4+J#pY4?95HeEkAnT>yNhS#fgF9=GNr+9aCz6&`}dxt>!@gD8)Kuhr#+n=sljZb zQfF2aV~As6c)#Ae@~j`E>i$2xy=72dUC=EE!4lkp6N0}y`02sC? zr0(^^>!tN4?0q#aL62kYzM3?IyXUP59qd4KqYDO*4x{bIRI~m#WaIrFkz;nEb(Jsl zRItZ_Ou!D2#)!TFTE;>NtZkE8rw8GQ?VK*NBU>HbI=u2o`$`AnI2mut^$-&u7Lc@> z>(bw&ri4AbJHNK-TTT>Nlo>dgc_q=L4ysMmfgnajG*Z1|qUr{Jnn z+BAE1^t6?lV|oQRZ+*Dx9+`H?2s97DP`0ujFQi-!t=)*amRv~Fdok4;oFfBOm$QuT zLbmg3GuhVHSFP4@xghcC(zh?R1J2eyRBws$OboIYO1*YY-4!+0Hy)!e_%yw%>tbUG>OQ5o(fW1`N-(=iZ*wq6lx zMHU`j*e}z4Pn(V$-u@I2l+oL=yk7&K@P;tjhG?cZ1kYPavn2}*x+lNvme8xo)cpBG zCM~a|TbNgP?OG*K+WNP{{qf6nzdK z8%r6=WSHEd>fk}uWc;kkhFyzjq81_;SlxwKAqF3QNwPXQ<<=B%}w06I;7IozIcDhl2kK}Tc1yY z;3C=AFg=Vl3P=I^%Fl&E%~Iel_=4#u7<+c0};$jv(=AK zpy|s1VT~{p=KP1^mttxDjgS`;GfO>pf5#+d;^z_>sCA(^!&+zo z&Yr4(ktSr97e~4kI+XM`i}gf}L=oVVd6`DE1bLyheBahT-C!=xfyvzfKO6emXnjbp z2Gd-hJ%O1hh|Lk}5BiGGAFwh~9^=!@ zp$g-V7S=#C2+JU3W|mrbZnPGML7&=)WZPCj$l-aTNOiaOZ62v@MhHA04wJRI4eLxT z#TFA}CqYNk&eaKba?Oo?yn^t_us9wWX-nRy=GJ;LXt*naRajnbvW{ziA}k`}HiWIy zS+8j?)qLTPVOT>qupa0|<2_lC*0PmL#G)`|CcmpRf3(a7h7p|OBnCSQH+|TI|LO)( zk7{9_1>>r0XTb19H_x=5`CcvkK%S0+%l9Vv5-=!PW!?)I=R%>vSk#zc`N`{hM8G;ru?6#1fE?@06Kb>5JOx5XnXEPYF+3kSy9f;CHt=64~(qfb$J_H|lb-nMm$c;El4yHda(jHnSNXl+L7dzjLjJ!19 zU^F_Ym)`8-XnrSqdbTF~+1lPhXGmn(H~QP&^;VXw93>Vyv^$H)~F&i4S#A$WNuzJt_`Mv^fi;sK3?Xe*LLo_0?w@Wbj_LzZ2 zq1SjJZEJX)K8h3t{kluwmuVwJ9FWLBf^aCEt&6}2)zu$80wa&T-To!7vgF40xAylS zEN1iCqi5XAt1^q&qsRrkuh5sE`IdnB+R1WWTPE;#%H8V`8|SwO&v2v z&zQjx3WxvB$$>7cSzrCzO#-hI+N-j4poS9IL8J@vsdBl;F|N7cBJ1|kG^6>z^Yz#G z+4|g&w#Y_Q?-k>qlDYN~bY6GN(GzB>#;4EA-tRL_byXek{ZJ?FCt!D=WTXt6`^R4Z z?w=N{Gj70g{U^T{U&cp1ucr)%@B_fF>$*g+-Yy5bgW(z=Ke~HQ@XR{xwzwp<*&1)s zTZ9RxP8pw6sF$V|>G{pbk1gDJFlj>8H}8cK(5U#&u5#lF2=P~5RnXtviW1)~gSYM+ zu`E2#oy6peXNqREN?CkY*EoKEiK^_`l&B_4pvVf=|4v@_2C|@mWy@~S2#Y6cabYFN zp+%-%&MURYp($RMTfLaLu9iAXUx>>r&w*lNfr{Aa*gZ;LZawTS@R zIo!022H{^}J!dkvRlsXEwP4a_v3-!IYR&-2gG2D(bzNgb>#!)vW`c+w}1^40g(iXSsi3cxj<#sGiuAaS{ENPq7B^e zc?*0uZ<+CwRps%v2e;X8J3v+fc$^oAXK3*5P4 zDKju`eWtrg#p!&UAllw7rxp)_$gYi&Rk7WD&SI)RwyWJ?AtCNxrpeD?eHxhVt16Hu zmQ;l(eZ0Y(PHJbea&v$v6is5hAH$?Av;P7?x!nN>R^40HHg+%{X03h`RUT;C=4wMi zidGZgLk9k**6}4}p=aosim5zIuUFXdeLu*F3}@;%O{#y3pe+g#S6;fXoAd)O0P`S4 ztL&fD`#p;(^{{aGz&e}#1@!t51~(T|XotFku;3C*+|F6gkrvNroi1d5_+RI@;Y<-CV^lekeqwfiT83Lo#FXo1L zvdGPQrBew)b{d7=5`NH~6%t5_wVbrI><#@DD?jOh-y7vju&hGYZAoIiYIcVK`YWZ`fSr+G zyk7L@B+xabE?H*nDP2JutAMP>l^tkC(rwi?an)uWQTk7 za3xmVa@AqqxM#Zh8$u%`R4)$fa;evoI*V6-R3XVtY7lHt7Zsv7?=C!$1t-aNiEViebriFb>ByEf9-x5x2)c2zw^voo!k`e zq1}C4rzN->X-Eht^&YXF)A?m@})?vc+j7%h~t}NaB4%*uDeWlz3Bv z{TCfw*U{N;_$A;p2HSt8zp+s5@bD?u;hE^JCkWM%%)R$bWhr|c|3mw}2*V}=*LN%N zXyMrb#@!vY!G4c4EZxp=7Ln88c=CQMvv3~3=xupMFr++QXyMpTs&cJ{P^mBXr5Iep zsftFWeH495PZN?m7v4_gJy6F=RR2=ujKtgrtt_l<*O;rC3(kFzg~w$YUkn+~(P1qzB8=~QX9u=PQw>7eFpFT|Pz{`D~cQDCbRO7}%KJf;wjaeH3Z zq-?bO_em+z$}=f$ua=4pI{!m5IThwLUgAb}?wQxy%!F=m6^ZXgt$9e6E31C2W&F#v zZOQX=8V6CWfpF#C*v9uCVS%`7@I?q5!lXnK(a#M18f~~?h)MNcq`AI1_-t5dV~j+6 z0w!ckEO&Xwa+qRs&S$%0i>88Rr;0eMeaoe5Ij3QyxTXy&qui|0B+8Y&+DZd^-t9f^ zLEn$hvTXMg!~`MDZDz0s)EYt)(%@7}U{%c=l5uWY?V1^4ggV|JBnTv2pAYvJ6lYaD z4yjt{;UWmL8gx{93*3C9j9HG{BD(4CthBxCc3YY$OIZ_pua>@CuIK-LKVbH< zO@R05S$^;7uxg0DF2uXs(x$$~^UiRef^mKBB3+EXLwS4KrZRhbWs9nqv_CK9HO^e) zdplBLkM)9LH5SxceSt-20~_{oVzpP$pESo!rnq{%Ij{V4_OJ-9B4BX$%H=(enyZ;_ z@wyz=THO&78Y?V0$W7Xn`p~p;?bkaZ9S$x50ma%eSb}g>osEAP=&p7dL51AS#3uQ5 z$NLhVa(%R|s7Dj<*43F>-yC2o-8td$1>f1`tB-cj#wz|C*<_qmO(MMh>agu)Ltz$C z{&T%Lo7{S_dKXah&y8JQYclCRxisSPthRYrU(e5O5)a&kwv7Rt|3fiYmGyh@C@Ku8 z`}_jXPhx5>_L+_DBss5aZYR|D*PU>t3kYSpvr8Vem@uw8?T><=o|^8juXh&(=T5iU zSm&#nI)Aw4LsZGS*|6eOYWId!wh2g^6D$rVF=#l0lFkStp`(P7P$e#0z?f9Z5Gf;I zz@a(~*FHrOvWUq{_QByWi7-6Q?T$n_$li1@zTB+`fGApcg{;3kVXmE-zf^H%F;`mV z{w>FUt$%Ln>WB{|fr4Y+-Jr9vxi^|?eM7EnZGutQF-TmcPa1%4Ii3|}DId-ePU?Hv zWsP%bUx$>!8vfRl2dgT$dY$66BFxmW`BmWL7d6>X2iaZ-fo{JY#{}p z>+#{=`}O}?v`u|td>_duU|pp1WXh7s(irNBXwUSow{0BLl4#3Y+ec}gd!le78=&-m zNPSu)R3G%#_5^#)yTATidDSl??c_2!5&h>uS6OmEe>aTt;YndzAccz}a68PSz6sdBhKpyytFZdIvXWQq7y{fBf z(#4WWl8zm7s1@or?QOR1_=b>smVE}-5jca<2x4-+?!Z?_=+Y%LW2cz}c~uAeni`zs z@Pc}G>$Tn?pj>e~l)ym*n=eV&tS@1Ojyz-lL~2hn^kP4i=pvyJ*z7-X#O~Ba~CMY0?Pw|AdS; zW$*TaX;GJF*VZ=fg$9ZOL#%j4Lh9crvvg!GLB_)xz?QV#PMP8otM$atQeUmFrg<%i z4@D|3ZRX~=o&v@~7%+=We$@^LPTe?^aJg^Vn*7LzOkc zT5$!~Gv~m5STSFH6QD!i(T_V$R7}$kq1CayfaTS~P{`fjoE)*?)8Kte231otn3^ed z$9hiu94hE_JRn6RpbX;}_bNHZacGxu9x9n-W!R@kvc? zgDp`)w-lnCq9-;+`VRMgs;VInX;2PhO{aIvr?X!7@0;6{5slFr8!nj$;@4tdH>gG_ zCRN`HL8JBB=!GLqmXmTe6ah3!uXmI`ei0T=r%|qi9Z|`vj}}W7KkqCLR~}2V5bIWb z2wB2Tig2{oJTlqFCWwt0y{)hm7#w~M<{wDXmgVJ?FUalG22eR9Pt2U_t!ks7wSdKQ zdylSY+yF`8;ywfOs~M>=(!Uj-r%cX@zy$F8R_+ST-0%_Bbq$DB$*p4#K(2~x7_ zkgA0eJJgQ7@tRU^T2BmNE>DHN>ewHU;rc+}0t3P-Q_Z>ime7@Lk7e+T0$zDrAEw-- zy)rUo-0JN_?^K3PATe-yA!x zQFDR<_S$OUBbyVQ{vaLVYL_Mcvk(g6{V+JTisW#TgOd=7g?c9x6%XfcixNq(nFvkn zUlkic-Qji<&~ReDD$Z!BE(9WE?yAjaP_;QRTqhanvSJRbDhgUkTbnWjFxV^)+V9s)Vy9jD+qmJRM*|#^=BMj6Mu0LHBI-4uU=o{5v3w;)&GGSxAmrog+ z!W*^Lup;&(f4_$Dbf-0V>tj9!D>#Yv<7va_n)@{n zA_K?|B5HB5DUVi~g(|xOvr5e=yH_K_{PH9~bU6G?rb~8Hukf%fzI^Ie@761kV!1L3I(m@$>FT+eCo%kbun?blSkO%h_3t1za&TZu!2V-K zXrnvcT1<_wpPZOKmVp*1m$tlunOIsH?hM2TB)U9`xo#= zti;?+vz?fXEI2zm8(*Rk_`Qe>Xkcnpw0%*hx>T$w7(ll0o*4-yKLTi+1^(BTz(vFb zjq=+wjoh-%M)56VD?R7Tp+o&Urvxx5sHlRju8-0ElQFDYAK%av&&1*``sEH}3L5~a zx_$q}ZjeFHOcWjUSqWIO>>DRD?E@CyUUszvPhS~nJ(PV(h!Ej^gncbdI{wO@A$?A) z$%6s)z)wYChK0|x65!#0#2>uLr#<>%fQ>UPI(vuMd1Ksf+Zt5)fp9WhJsQ1;?z7Ye z{Rg^(-k0;l`*)?P?-;zE{d6~=FnER81!7{sYY})8&#=nCyd**YD`0?a%w@&fx?^=; z*S_r1UwP|_4j`nl#h)xUAZ@BXRRXjzfU*Bq#NmIT76W6pXcy(T<>}^CFU$i-R`l3x zURNH3Amt5)rv;DdLj9EJV!^4fM+q*7${ZS)FabKZjn*4EeLV;LPR$YUg?`(nrOOYh zbW~hUioWn@7?P?!zQ|zZ8!K_&l-H^EtH1f8$IUfp*gk*$|DdZmg87P9wrivr_2W2k zLMA`RuoAHHni6RxIskd1p-<;jolq*)ONR0@?d+dI6f zx5t-+0nAV_Ixl3tK2pyH0C=`Fy?hiz&0f>fGoGx_Zpfm`+*mmLYc-BN>Ucu2=EtA& zoq+6DioVgrmPwb%x8$koe~E6a=eo)XFIXpVa@t*4Ni}4Ze$1k8L=ODr=SMN6Nuh*+qlck6c z$77BJ5^Q{+%4q=tyFt|pOLM6$42C2~e49OuBJ?aAa6YF(oYf0dAmN{J_=H!$Yt49p z1b5(4TsmDM2qGqqnDUe&69qL4$r?!KVc+yl;%?^X=kWhk3jj*z{g-(sizYia=g%wF zlzv~{^>mCvbJXBviqEHVb-#{2garQ4u0I0FWh5XkGhULRS)!sp$i77d9Ti|jDu3%f zwMBRvJwy2anvHhT_WuxM1P;y37Md`@8!Dhv3bxF%zP&kP+!m>B5vQ1yns%WeHM<#X zOc#;`_ZtLFL)c|rAHC&`Vm#(Yq6f1F-#dUkseFbV^hs>M1iCZ;)2`4J+>Q)G(h?==%EUu9%orN zEH9zwVp38nxhL@#El6@K-IuiKK4X!FY(=s`>6G5&0isZIY1oy~6JP$EpSpb!+*Z#+ zRar*`soHlAprAbwcGZ{Dw=+$jL|ZmBB*)ptLb%&b0YP)H|IVvU=PfdF@=K@BA3HY~ zkl`+oCByj25j&7A8ktFB;=Q)#kgZt6CO7>m#j1?wmow%wyjp*t6;d5vJz4xv{9466 zu2VzHMF!ijEcKWo_@{Sr2zZ;}(73Ld1}Eupb%x;)-z2hG5KC9Z6-804qOkKGS9*l= znj+CeC_xIbOM&`=5as;dLnj6XrquCBD~QfhTLpCuaXW*nY|`9sQH9GM(=hwFdxH3m zG(KoYM`hqgesXft?I+nEj*UN}o(slYh>4&q8>=x*Czz_GvYq(-q=_ZReCKw~=7`N) z1z;StndH&nwWL{ZfQZs0h$RS1VcWDlOJM}!do)bw^4TASk1XAVOJoL*G`}iNAcw#{ z3vQ%$y-S>OIp3+A9~AIHolMF*51q6pn%V|;PmuHea@qphj-iYz@fco3&nS{2r+3(q zZ2Nzw{0NX~{eR>s^DMnjD(|e_*VgW#jmjg@US^i0#~+JdO-~7ZPm6j~heqfb$tuWo z4Mb60cSV|UWTrQbc|k0sIccI`P`^2$om*v%b0|LXGTrkR$&quulr;wn0Nx!N@4eMb zrP21_16ZVn7i9mLu;IBoD=;rx{6Mz!M(f^%$42Y);S z8B^?3+OT1#OFfn)i|>gH&d<&H^~3Tz{Zq_N{F(Xs{2`zUSA3}gSpVXM%JcLVn~F=n zfn|?b13fW*TuDKyGj(a?_2O2XM+OOBDih^FK*FCV*E+ao3e+Qu*gC{-Dcu_f*Vt#K0n zMiUBxzHvioWmv_OD!1CzG=CDrW1Zg2s0O*!G9$qys=e_xP?c$S(Fg`SWUPR~e8_r@s_N8jS0 zeEr(2tg9PKgqGwk!YyH(a!#rE*Kd(E_h^xsyg?$Cl4np-seu~ z(@Vm5>1GJeB51SUGBd{DLcX`b|BX5Q&pF9o>=|+9L;7uUtR3+N;7r0WgMpX*+1e=< zQ1XLdiX`?2IfUVw4F9Ydv#qUd(d^o5#T@BY)$(@-tTTU?#~wg>NbiRhGIy>n2RG|) z9fZ!rUzVr#_~YV{k|g*ROr!OCJYgtW(858a(cc&||2#nt9SuXhJeW=v6A2OJD&D*l z>+K~|@q_VC7R_Lc)`58et{a;KrxoaWN}SJ-`k=36mckN8?Y>IsEH{Z;d3kv_4ts1# z2}QaVr5nOFTQmEchGPGFz_B~0Ez3LhC=5bQ{Mv_q4)!A^HFfXO>0`%&j2dO>S=18g zN}*Cu6d!!n3jON%dj=VYGo;C0!F1`(+r!rGV5nxS-HZj$UT?E%VA3cm$9l_YZepM{Lpt zH5yX6g^Rco1^ATFYOKciD|gZ%3j^W);iXzLF#W4H&mgbtdF-ZU`}5=W zd_6b*Lt3~8B<|bC8{&7^TZ?!a$6Sj9o~(zCI$9-wGeHjJ^8)#;m~`eHI+s-!y*fR6sC7A@5M)|9n{&A0O|+ z`4N@qiMl#Q78x0!Nt|i+tlX&HP;mCeYVvrwlZGI?|8mte#yZvMb`wFbvcy70 z=N<{Ayq&So>@6sP*Kljz^RCcS5c95-6*&1#HI+w&vyvWW`(x`IYFl~EgCmKw-IX>< zQTgKd9xiF&J>`E6$Rd}@b8*%b_iyiC!WbMecszrY&MgXRXMOW8$;>m|)7M{2vAU-Lu`N4l$u%tv*_PvHzlGJ#Wz-950p9)7o;rpSk=;YzkKJ;&nIMW|V>@!Bn*M zFw$O!2$+e+pL`)QHCCHxFOGc;DJT1y_%y5ho8u&-ST~R z20N_InV1so-HdY8--U!0Ssxvi858*kpdXrJ3_G5|!u0H1cHL=XykFy9b}N|ZthN&F z>sDI1(t4u#*BZ|;S(>I*A6{FUTl9tNIxOJjUcx!L z0DNF?)7z+f(2?uu0gFN&>Mhm1t;6^{Xt}eH6I!j2E92xul%@P${k;q*Z$-xV23nwA zMq@3qgN^ zx3l{$hPQ<~NlLPlB2y+$TaIfB*1iL-WsQ^xsd3Cd8RT^*V{YHS?TrmOIknvpb-b<< z9#k}^iCD=TGY%~*m}JLH#g2t6xK=hXWTl_tUG7)dTs;TvLne=gV82jl&>};J`XW^AO5rYo2`m(z1#R_mY zv3=0N7mea5rlzNxpSg^ES!m(8j;%A_tq!K zCz11gB<VkB_BF(sP74@R_M{2z%T@qlcX(**{*y9Kx2fo7^!#|ufE?V;1Y_(B)C>SQKM=y#b4BC z+F;`c+9MMbR$U}o?9mRAh{ zf`XbFtpAA)Xc#3O^+v3C^tiuY@f}Q!iG(D445D~yl>uOt()tY$h(4ac0Py@+tZ6|x zKI#XNI0lkWIv5f;%B3M;VFKlM%DS$cMoEG3a*IR*2grbhbjMS0alH(Nas0|l)PEMo zbUltzLvBw}(N>o+#S6I_#urp5A*+G8WbF4Nbx=BG#|c`Z*dY? zGbThbohPZZQq)keG)4iU33c}Va@O;eTwP(G-rNqWi`1#AW6Ac-e62czo7C~Q6vlWw z`F4oeh^|IXVo^45QyPG0$tn{tppMSelQj!v1AGSP5M0Z@hAVb|T0b88Vuspe| z_eU@UNk*9LxJ*D0S!U$f{#96Kn42;6;o`mhNdKw3Gu3m}VrXc{ZPm)OURFbc(;o>m zd`H7xxq9G^~pVAH!p)J?)x z-6IhWCtiqDs~cT@vqt$v1<4AxU@Gvu z#1xnf{Zb*-wK~r_yqoGQc&{FvTTQb6D;6S?BTw*1zZWY!{l8U}R_qQ>RR(hPRZ<%5 zm20LU2dkInTdia93^8Uc7tJs}7NiYKn9G@u;%Idw(nhR>{p+{^NMmOJHJl)~XegXv zPPfgk{+HnW(&$d^YbF)?Dwp2?kN}AfX#|JS4^0F{*#6X~=j#?;cyWHU&hydBr^%vL z`)44OB`c%h$15^+8Y;6|zE9i%iN?y1RQ zK@oGDD040B!mpY6>3=H_J=|r8zR@_-bCTTGDsgV>Z6=({377wvYi%1bqFnUh?aQA` z*u|zL_#A8{+(>PN6ctI7mGV_Vdl(e1KQ$hme5bGxP8MaFP8&!a@hUxNSyI2=zKh?+84K zfK)%jAUZWE5CD5iM&*r(_K^i()mbTKoN}(f7^=d`X%aU24P$^gZ-Dv=n$gO_c6}x6bFo#)p`i>q;*S+FbZlNNnG8yw zTI}}ToAB-Qh3RD;{?F^XpJc!uv7KcnI3j`FwhcG>BXsf@F^mbOK!pZgG7$q=Y+fPE zQdR>yF46v(kWzzpaLr*_t1L)ntfg%7jE~M9AXa4^VhWeR<=%ez(MlAgToB0Hu z)uDCq@JN-0%vZG}Zn^o5XI&w5Q3T5R!^HJA{!b(jmJIWa%LyAJHggp(GhX#^RMAgM z|2GoI^x7eN+O%-6_W(q%rSoF3MVWzv3s=819w|N*yg){deHwf}uSTm_>0I_*Q5V=E zIU{GS@`eK^?ZL|0j-6A8+-3$Fu&Z_T75gn$HJ26_F?0tzRJ0wcS1@<$q~*!xu`iJfG7690Hqn(?X4|LcYMSlOX;D` zL-{;1EMgS#!3^(Su96m{6*kl-&g&LdP}cDsJ|$qM2vL{tEL@Wf8WxMrg5QvY(1d_b z0?TWH58xL&304IdbDw7*k@N7xqz{2Yw+tFmvji;fI$o9}IN&FBqg_Cqp}BNB|?Tz^{fYz40@ytUW%$+ zR;(g3d4w(PwVvA}s3R1a6zx|1%|Q^P%B3u5Pg(_~OfAW$kqfG78lR__XCs&m`V0|I z3cC-TY&Ukg?kt-OU4c(*fCQQxxzdaV)&M0Zmyfe?Fgw8R&8M@zE2m;ft7b`5P}?RD zUc8;37j`scRv@9>s&GKJn$~5p80mFzYELPRR4BxE@6saT>~!0*pF1l)abv;vS1J1Z z15Zg5&CAxg++(_IcJiU9b%)t&XLO`rwifI20j7=4TDb`3jq6B?Tt^!gsK`ABj!= z&*x1<$dhy#nb=d6AGyo=yQXMkViA%!M3z(|4g0L#5E_WQZWK{~Eu!WLVR*X>=Jis0 zpcqSlEJqQ~ZI#7N5;ox@%S6Haae8Ly0F(32VYzO{SuWxFx-{Ha>Wleu^ObzMP z_Q+Tv&LN|J8nIH#5ry}9F@zr`1B_uq-klTVW5Fr@giTpgiAgvhG9FdG!fIieK+bUe zT0=sDn4_RuI=UYwb7v!-$lYe*E4IE}e50=Y_^^)AiYrO0&Z{|g&IA6pecV}j-RrHd z0?X_wDw|{WH0j`1@%~GQdxvW%Gq1a^x=IHEknhs-`S)mgKE=Tpyh-e8^sBtGAV`=9U zep(!7GGLwx;1ov5N$vY`dW+U)#lYdUcV5omU9H-ZAI$c)wmp%vFf}lTNj|YqRwN6k z2SWxOl4YH}VVe()-=IR$ZWQOQ6UgQB<)wEj@HWERM0ac>Wm20+NHPy1T;5y2czZmb z?=*wzsd^kwOOm^V>+EBK24_4my)$|;rN6)+e4Ds#b6q$1!-FWY3A560iWu2>?6S2y zO}^q4`#T&c{ydmq4|O@8PFkFMmmdD+Pq*7t*=X3Tv)sKsetm_Y(=}5a!V+LhnYoo} zUVwxIUf!n%tMN8c!20Gl2+_mS>8!#%AX<)RHk;sa4^Y+DZKc*j@cMO-W238Be|;Z7 zR7Ay5l|?+4}rbZ5Q_yZ4=K|*=k|dh8Q8p^ z-PmO-v6Bh3f{5UJCIu>fMaB21XsfI#-|^5TZz&j1?k-PNOv4|KOC_sJSIC`k!`7oL zcvZ#0Ty16oQghO5*q+*kmax43#JWp`1u#bSbrzCETxZHVOQ-7^_ z>y69%4fm`lba{r7g}iR1TM+&@z3yUQL$S_ti|&-<`1_N5AJ_9bHkkw>c#e7e@ALxj z&ejB6i|1`U&A*t7HU+d7ebN<$1pBc4QH8Qo!``Lg5fQd_cDdQvKF!U|nUt#Dp)zmx zXhQd8M05|fkScE@M|gRzJ8QZ#CRk1}99*3eIcq(5USZ6S%xti#192#Lg?hv5i#l+M zr@Ilfceyw2DdVp-F8b5=tTW84_czB>3f!aUc_i1G$%&2nmDj^QNv>5Ddq>W{4G>XU z$1cvsk_Kng7R@P6B3+l3$Vk+&>issoT`BmF6UP5l3otWNq_B9iMp^b`Ow@$?C62Xw z0?truwds5pz3CiYBoyAezqt}9IFl6+ib#EcD^-0=sthhRd1qo3nfEjN^6R@pX3t-o z{GI?^zX&nT`k)Nne)%)H#6x^5QMamZeSLlNuV9THj||e%(jDAqa-Q*xl+&u-H*8Hw zVPd0_q$&v628tUlqK|m%M!PvvC883Goo~?Azn(OlWVFiCi^U$o%Uyd>lSpDzSAIiO z^F$RTw;3FPDDEUG{Vb{FUphfeR8Cx2S_EV_TJl5tLIdZfmX5@aY7-Pmt@r2QmW$hc z?ei^M{ASaWU@e=P`jk=fCt^;3JTWm_zOFTByp|l6Wj5w%RHQ7d z=m&+bzK?XFUzsF@gAjGrGKJ4`BCXz)EV{vx9*wZ|R-AS2!)fVcPula^1L+!Ae9^Y| zPB-)OpaI=|Ta`bZHF}u=2ZbHV;(c$S1*eXHkcJ^Bo786hJo=_qYr?FxrFlcX5{Dj1 z+_OY6`y(};CrW@a#qNUKk4gdKf!_rK@zA)M&iRH0G80=Ot~O-|cMQ}`Vlf^qXPjH! z=Ad;XHiPSp!|M>Hwy-*059D$X1>n@KzEA>ORjf(5l*2?u%N%oW>0(lR8iq}DMvGaE z*0oS~&_(2;#Ad@B?cp@Sz$A#=#Q5+eZ9hla%fZNYT`Ig@emjpCuD1B&Zo+^GB?85y zE>*f*URDMaFdU`~y$Fj~sWX|n{qPQyg2L9oh)Bo$S>BVsF5Z4&{-=c=RqM~E=2{sG zFiT_+My#>4v=;~+mi3jJcS!ZtHcMSy-bZV8R8lbvdBZrewbr*YQc0jTFYU`S)UZF9vB{n zhee<&6vMzX3|JTwz(J{?OZtw4E7q=sJ#3VxEPypH$f(_UDVF-q8_uC{vCsPD=B247 z9gnuU8(*F1_j27g6b0gjm(i6kjn8%GH98+*d`m|W8d-9o=@Y7PZf` zkHvWX7G$Pgna5;86SZ>aM{~!?!=dTh*1*}UIq5+0nSXrJyzT(b@gVKSV^(J_J0_Ko z@sk@_FxpqN7L6e?@3BoS8G%1Ze>pIZoXt{A_um7`DqqlDx&SVrP6wNwOr=r zh)bW5JSqosz~Fqj$$RgdydPYv*M#$I${OT!hF|dccRMI7z3ED?O^m*VQ;oL- zQwn62oqa#3gr)~1{bnXH*W&^~9~&40vAFAU{-K@yU>mHmuYHsA5m^}}a>y_Wm?#9_ z0iR)S{P1a&IYXeLkyWI;mXH!cU=VN!9KZ1iAhtOi%8x7kCWLj>l=tPi*l45?k3@DqvporFH3%!NT`8gZ64S@!Z9@-I#So_TxA_ z>g&iYiF@39-P2VJolpPj|D0}k`phY2od+dtl>=S%w|5Y=#7>O5G3Jazh48|W{%DAD z+V44?_wr{j)ULa7wa4J{$$204pimRsst0bmX7rJwff4Go@U}rq#jzSu0!ynIH%8#9 z$iph%g3e3Kej?Y1NQYC^?{v#D^-FY~MO2{*z3+^8ZZQpSs1B=*-+(ohHnoGj*L=pR zgf2GR@bd!KubMIDd(F*vvd)mJcvR>XcP}5Onc3M@H+)nOKoV(E@m`~|raNdex3;JK zoaoz^G(YDWe2k+|t|2KX`nkbx`61kEW3S#YmCYs4L-{m~$)7}RFSULoThe-gDWWTs zJ9awu*o;tVRbShj_H^Af(!Qr-UKi*Z>-$#k{%k#i?GjHD5L&F$s-QLAm9@nCrIU?L z!lKO4jgEk{F5|FuQ+$N0)@_CBoqmt4K^7rm${B#%lGyUvu+M(Z?Nld?ac*mCQCFYY%^I zq;{`u(CG&@n14^I`_Wn`ZY_2$*HoQ0z*j_%} z?2R41EeW^@JvAlKHsh=PCM!iPQKIq%oJw69RmDP^3sFeDFT8vY+mpxcHhc5JkzFvZ zHo~6?U$y8N<+m6NhzNra1QX*riJ5&8w^kT-z=RPwNc?}`X)qm%u-S0nX?c#%PA@H` zUR_;fQdUvN0xH4}t_7;1)u*f}>|S@8T<9WvarfP_u94sgaJ~ZPd+jldjnj*X!$Oxm zdpfx#htJ9x!jUn&HT^V3xip7R2_gzbXLSG~9)}REx_l9D*-;#>l4&fWj+W7! zls!(=@i(ormzL4>Qm^&7c;JB;(vjN--Pyw}J%5~c{9qFvV=;j4Y10^kH;-0L13^}< z5~>M(&6#(oXP1_7GgGZ`2!;Lf5<6YAw6#9{7b7YKDdN>Zsbsq|P39~9{Qy2i>dGbc zj60ovE+yvj`{*0fWdzyhUlSQmF7D4OFDmU*VUOeG-xaaDMDWguN;44M0$y>$K6jA9 z;*ly8QU718y>oPBP1HV^bgYhT+ji2iZFOwhw%xI9JL%ZAZQJSJ?S8*+X4abdbF$9L z%{n=^PVHS)yPm!GQ&nW_q#-)kY>3GvAkvs#)66`ah776+SWM3!fyw-Z0p1AkU*?GV z-A}*HR0_Y^%%z9|doQ_5S6;|u>)LY>Rf?O~bg#ci5ezUECp_PzQV7| z*6>B&ZJFwUU?{1tfzDaJ6>19#*_gfcCOt$YtFa`tuYO_>CW(oLpQ&)A zV^uT!g6_!nv>AyYQ53ZnKU7D;)^#BK;e_cy`KMcES z3%C(0oDG9HTY|-4@Pk38dvIY5(h52Uu~g^g4qY7ICVn$%X9mDX$jh^I0xH1dvr#XM zV4Ve--=|V6?S9UUH{zLgGp#gmNBbAhR-&VcBLj>9s9@{j0>Yy?2RSv22qY?~;0Z8M z7&PFC00JP^;9z#v-dw=nGxjaN2d@6z1^ffD?@}040r1`3X40Y1zYqVoMi2zx!N0`; zt`mj-ifCaEPwDw$PgSy%aPDN%b5W9SvfjG~-dovPC#wW#6Zle}^OMY@HI&Y-OV zAS*Y1%vuI8D;{ z#lt`5JRkpRDF~1^&;O^ngm%UWEm0Q(%^K%%uo+>k`Zs0V*zQ!RzUM<}%RI5Vztw`& z*5L4`1R+Q(dd`x;X3c5p@WCcW{S`O46Tx6$xgrzuHJ269 zptAIz8Zxfn^V#x(_*r3Uusgrh+QAs5zpsEITCEnbb~sDCAtEfKCX~W?J%8%%jdR;q zmT~LKBAx7qz58j=hf{%vFpLpp6_DSb%t}qA!{*DT?4ltBRgB~Z*um8Gxz@rQQXW%W z9Ijd%E_u*6pnTj^FlL>o(EOFN5ANhk!*SezGodHo=2-vlJiqL8^pjMG*K2pq=ew4) z{s0hr^MEnCO(K)Q2mdd1B##PM&}S3bk7$d& zk;OR{=O0r8wB<}PC3M3&efaQN{^WXPf|U#!{UbIIbZ#(61fa}-nJSN;uV}1*ZSV~) zJ5hu9K`i;NgfWug$p^sZ3ZCvGoA~#618yRw$-3|a#C+VLoTWbjE>LePT zH;h)OGEkoYS0;8JCHG@4NAV^+WsNK9t+ZXbe_x`&p5O|^H3|2fh_hR!mFFOaoZ?P&|R-zQT!u#Z7p_2Fg0wm$)9&orl#>h zxwO3Q26KF*)9uN6LoE2}Of`I5d>EmlwR>V5~{D}cOPQNRxN1{dm)#snyiW%P!O^>9c)40atlO3ioFe9h&cD#p%|QA zx9|HzXBJr(=sEfEzgg9tft0^V#*mORP@BQ(nqmdiq#m0kt!8j;`FofSRvk^-UtYW7 z3gMNwSQB!;=oXI|uHdAtwpkKd%JEn=8Rd+LDg7jPzeC~SsS1m=wECR4C{4staykM{ zTU^f3aC~J#+NVPDmFXf2`gY=2@T!k|!U}|Scc+Z-GiDCb0!}_Y{l_VS9=IkcvK}y! z8ve#Z+K1Pv1?SOsml3KtN-LMEY-JVsFs_;J5KsWa_k-O5R(E70???@Q8-I#I2-UR3 zddF>l+it=eb1$FF+w_Sx{+;Czi3-(OT@v^|3yaXM0uQDDgnPQqZ{WQN-It?!KMDac zYRL?_g2?k5d9#Q%XATOUL5KRpv&XdW00XE>0 zCVuIBX7nQN?B3UMa6u=6#ARV$+G$JL!=F444*@$(;MtS{%wnbuQ{AJP!zs@TsAzu*H6$UWI*1> zxi2qWIlz3GCvCxyiwCUr9O*@8-!r*}zp(b5Q5(KnBsew3hf)!kdT zN&v2xJ#&f?IZ)8pnDXHjH2L|0tBGR~Egznt{BMX}dN72s%>XN=J+#|YEZ1Sk)CQsy z>0|!!G1KUGM2Z^=yx~skTcy?*n^}ss)De2I@ZY{UoM_=1JJVSoEe9!ER{HQXOT;~6 z=J=)C8@bhAX9U^LLhe@6YB+ir(sA?+W7WX2s)#2qala@E=LYL1n51Jd-@*J#Cg@x1 zzA^TA**4t+>_U@c{Gy_?Gal-Y`1t+B3ZhimcM?7&4IH~m1y0JesPEWej*5i3F^8G_ zbVK^k0W;uT=?R@%+ObS9j-pw{`CjXbSw)x`gfi?0`eSdypwj$m@%z z5RICULozo(?5jBkI{3Zi?vjm+*n*djWC&Lz##T2n%%>9aldzSP;^$|ImRF*~uRaA8 z9c0`lq+*FYRWZg7o(@XBx-NlM2jE;M!5A9)r&=@bv#bg29{M&Lqh6}CIGv4CbcnS? zw|QaY_K4nCXDY>{$^8IL;#xcNtTcLr^^-eR7uMI%68_(ecG~2t+I+@z|t7nuqrx2GJzVE z{FvryUM!(>)(szWdHI~X}e2=C1gfH zqte%xcy#d+-=6xEe@`ZxWp#pDftQgF#Y$|-G{l()+h?kklzz^v6|N*xdoyccX`)i8fWxiL^+uBev#E<9 zs11=i(vfa_cv2Y{DC4jnB zKQN0$HRs{0|2)Nn&y&2`Z>?aOy(8do{#e6{KPl0o5fsxt(5!dwL1&lY!DNyIw>rB% zdRk;G4T!e=dxzN!h>qjm(AySC`_VrvZC7!cHyZI@1^)id6O64P{)f@YlBQsBktO3i zaTzn3?V48^Ai}HYdtG7s*Jp9UyCeFdm~nWse+*tb#C87De->fDUxYR@NV4)8%*Z!_ z2~8!xJg&Jim{zA0SSP#M@9GINo56w1VhzrGRca)pET(3S_V<;$&yr4)e|6CQ3-4@P zVxu9Rc2=XY)v2)!*<}oAP7!(*OQ2?IUt5$@lG`$gS4ZccFdmuzq104r2~HWwy5>h* z3rUKyCPTj#TqGc3YMu?i>fZ@#&enfY-h>kUCoivD>SSG*dhx`sX<{M3^GZacTq9Er z23dN43vI;>L;LGJ~+~6LDGMy7}Kj)BKDuOxmgiGUCEv~cv^Gxy}!v6U&4;=Ts zhEUC&zjqB!QP4~iF`5!5L+@&_aieM^Y?o#KHR=`Fe`?uJDwdqFn8B##E2*Lpvn~{- z{CA(=V|Vk-P?)qIs^^(8z_R&%EM{{9De+AzRQQIwvcDMyuYkoFS z-2O>J+U>X98+FmigTSaheqH4rJsZ$_^wr%fMQJv8+rO9gyJlV z)!-O?5)|;&3Khx8qbBseOMMB<@R&qqz%Sn3^!twIc;!{&66+de9MLJ0dLgb3Vx&@r zM&F zMAYA_*CpDE??b6K12D)S4g^89u1q4!H-$Vx@)G(XvX95T;;6jSaGx;7MN#8$-17~Y z`JQf#E&TY;8Y*6uB>CE>YoREcbhsw?YzcAag^DUSgqH%qb4**&ki(0z0*R|)xx8NDS^ zYbD;zn)b2Dz3^Cd|Jo!5cIMX`^ZS-h;h);nLQNpBFW1dbu_IBJ%nFe!=5R1U@Ay#? z@dv_*OZ0C7&S!khLrHsBTsgI0l2I}w3|?q3Aiyj}c&l>=2c)WQAwf*_di`MDSyYKD zhOr~Z(s*WKQ_y{L^O&}}?0Cl`WOV8}+R^^A`amlahRTmXHdBZVK5W(00)nCd(%<_>r~Yd75SH z7-xI9ZT@CjEd%6Zw@fBQ`;*)H@9?0NL%W-jtON4{jt$1Zvi%Rr&13=8+%oyR<|ksO z8BzmR>z{JY_t-#3Ztfh4-n$${T?=D{qY~s9P;PCKBb_scm;VD(`mW{SqY+gSyB~R% zrZjrzh8WTp{hbU>5rc9FgX!z3N2&N_vj3O`T(TwVZq#;(R)|tj@BR)qTn>>3a#}F^ z-6#jEM`EhItfQLxhnL<8R7XeXGvWT8@HbioUv7ELfw;rBhJc|R=VHPa5BC_+w63&$ zEP-zr8Vo*nB|!^;3+fT=cKkVdBR0C>HakP&?A-NJ*W?=O^3M;27bLMP?@b%w|QZP+oOi}6tltPUD_@mi{AH>%~%4l92}Mr{KhFlT)?v6^yLfv z9S>s9fh!XajMK@>I2uk^kXMHqg#w#d4@wUrUWmj31|?jaqN^8PZDJHD=Ibb_gfwDV z>8bF8s~qgAHG?cp9>k1wK_Z%x%Q0Z${09X=-QcXIl!P|DOXMQDk~&fE_7oZ&N>fkI zKZFu+6S^Fjh)G7?mwqXn0Y^HroDMS%p<*@;i~Q9Zm;|LN#+Ik7&c;hm&DQsf5K-8(E)!($#b zAs}gSPv068D&PuBm^OY&$1(sv|{|CMLxO#XkY2y+kOPFuH-gBW9K)J-_ zv(W5s!_!*2$$nFGNAMw6;_y7QSOTg#me=VI_JH{ap*QVbR8YNMlU2nP@5`D2?;vLOYSpTGJ2eDXDx^N;c z=Mm^Szb20U$mNuE72%Go;|LsORoAqnm^~9OTOp(Fws%M%sEa?e7mA&e=r=Hce5`jr zVxFMA2E#~e?fU<6EoUBb0g)MtwA1~Vz7bS*8SoAmElLSJDFp{25nyCH$D}v~*q>1c zEvtgj@#30%jrR)+x=TvG8iRR{*Ux_Z%$1eCBKqee5PZZu{6t8e^F4yj#>t*~gC`oX zIPO~oY`c4$F4Xxx|4gDc3TmR{X1#?HkX4vD*_F1$M=9CH$coAdr{fX5`Rim0pM-Q7 z^q}*BqTV#PPTEG!=WJU`kE=K~;R~eyhM8_RR}umXl`4?eyQ(mEiLeQ8l40DXz!l=q zk%~Ge9E5z|*`E8<`R6H5=pbH^RXCSR;`sW8@Uk`#GxtT*IH%r5TYqIIZ>=JE!||}$ zUA5N|+Sf$(b!=*6P^RouwP~=htTq zYPeCs;Y`zHIG$%_IlDwDPvO60Ldf=Lcy=N1P^ZNa=i$AR>=t708yC>v`J6EG177cV z=T+KnZ1?z*G1|~0gIP2$E|0DTsVeV3*YDemq2Plg&1FaO-cIAUqr(bd0k|5YUl)%f8dM;bA3d$7i+Mb zPoR5Q-vRBq_Qayh=*S)~Cx&`>N2`Qq;hHoRB{j~fym5&(yR~U4#=D$YK!TM@lQNR~ z#Q|4~*E8GZt9m#jAW^`>==bvYS}Yb7W^#+ju9*F}rZL^7ABFR;W=p_>yXmDh=i@*h?0!n*NzrUlini&WA4!i^FP z$XB{yjPkB+VNt&dV`@gr=!PgPcG;F`F-=mk#fU#pZAHTe6KRIc}8M3Od09R+`R>~c8 zE4GWF!w#HB&{*ZOqIO~u!ooMwdy;{Pq1WD=8ar{M|HaA`_M$|8=WZ{huRQHt5#csA z5^&QZE%8CD0}I(lTbfK3M`y$wvnu41L#S-Q8~o1ejKfL(!|eS)U4y!e?)*Ynqy;>@ ziobvtE>rHsFj(GiElFMR9oDF@CI1+Py?&5?Cb>Mw4e>|tdf+xo_gY!VOShO6`Q+NP4zzm>X?a zMV(U~%y*&Hw4V4?vt>;$O4-$aZp~?sSa}V{L_?!m=Cnt7yQ*m-*f?%(v$k9iSZ-ZD zDYpMg;{LI%U*`yCD(fpN965glz5GA%1ujkng_^`2Oc#7YIxM;@KJg!x`xL&@4ViU!*nbuD&%0~=IGj;tp zwF*D1q0sl?k<--g-n%iCHN*`h$+RvH8}=4m_cAU!#;j3+H91T{3O>bwLPg7x%** z@wTJo?sLf`Cz>skPxJRk#biS=y3g3Q1K*)-KR&_H&9ipPO}6S-y01FYojGUCFvqQv zQ{=uPAanPPm(TL*Ixt2rmT$Xjy!K-jBO+|;LB@7@5By{_Y-46}y!L_5+Xq&w`}$Uq zlB+5C+M`{47mc8IiazRAa7td@zr8#7_SU2uum%uz@9ECjfOE(@@E4yuw4n+%r`M?~ zj)>#_%W$MUyZtxA2^_0hb7#6>k0U<$;C^%LC7srAgLYw0XE4haFlm;LKI&C4b_H2n z^yQxU!`^!=T_yKB3B4)b560=N(!Ld2qTL!=WBb8v4bQ^!{L)JTy$c8U`_VNN$vXX; zz(|d^v92VZZvO!~>LHBS(3E;NbuPUcufPE+2vOd;DC6nHyQeW?r$?$49ekb)d+cct z*R;giFQp}Nl|0yAk}u4W?zt!4OJbWN8O5ZO&dd@@DR!~_XnYhFyWkRalz&$A2I225 zZz9+VlGkYfflCrZ;lE)ky5i6`2c%~l(!+Xs$LsrU8~A(R?p8p;Y`qPs5qw5BV%Mmc z9{F>F!{yWk$nh zo#cNSs*caH{yD@C#r~GE`VhK$TltUK91Kpn#x1?43{`Z zMp=EYe@I)MfVBH9sw$>o!Ue&8xf7Wa>7#WuLFR@Uw3+ForwXJ#Lk21@}~UdFzg7vk#h2d7$-5-3g6;O z%4dMv56lea$e30qvD#~88d)5$)ofSM5PMi-0-(Adn)1&BN*5yQ^x2Fmx5d0{aO1=t zmS=;!aRxA2h%Z{Jp>EJpz2ly+Bt)^?lG<4Vdw6_l^1+6|`HY{2QjTKi6SQ zE|x5<&cAE%%P;Ay#0gKvrTxBnrmC1(9K=X+N{**q=ito#c&3}5NGoF~{P1xXE{2IQ~r!ktNYn{`XO$?NvF{;@I zu;Y28nKx`79sViGW3;J1YCd<{@~~#P_>2g?-#QqjhsOLkKIGxMJL@{VpyxMu+D86u zO=HV@F1BWhUuU6)X^h%r#;vJF%)T*6pyhmjDbb9K-f(u_b6az*do*}MxZ<+vF$rfe z??BmfZy4pfzoHxJ|H6%KPuE;(31jiY;!xl>k8ctbEeUsC0XKGI~hr)y0 z{_W2<>zHgG4ywvqz>Y9#w8-?3EyS}#e*d$%It1e@MOui!Hfl@YlC-D!h(cpOvvJ)kGQ&9lS zAC2*pgQ?if#>U2hfq|feIiv??THo5h9PdQ(Van1jjLGNg;Z1qhRn_sbsv^Y-0dsR$ zmZk01U5Duxa787p8nH)9&~5F?y}nZ#$-~dIuQ|mOUWJhgz|_!=+eUni4P@gF+J=Vs zygEs%spj1V9yM84lx*DWEUIh)r~XT)hte3pp`W@fybva7dC}Gnu#-Zj{l~9}t!V!x z8Xinf+*uVTZln%}F+PyzB7VkV=Fx#&(i}B`RO@24#ik|pw2Mq)?!Shz)Z_lEaO#QB zrrmcR)vBbvH+hjlvnyJAyqDe;;g}`MIYVzZQKg}ke5F_dbLhdfLC~`-E`V(KK#xjj zX&SNQsH8&nSYUr`Y;~Reb~-Q%yQGZO@1pp~qpV=(Ktgs1U7;d$?P33dQ&*yA7o@J$ zzX5Q9BVpor+-OGExi?4}^SpoNAMf7d?d&6MEYJ{olXku1>t!u<&7b)Uy&Ij6&7TAC zYPT;SzNVLm*l3*%zYs!IT`#>j+hJuhD-B07!(khlMJ<+dB&N4S{J<7Ae@n_*cV(|V zIA%tTF-wFZ}fGShs+tr;cc zB1%Z&-vbQ|m=h?+sBy}op%{*Pv zFk*J!Wc`hA?S8gW^NtKgZ*6oMjBP^umFPVCuDy;fXic-+wsCY=ULXy8B8iRNEg)r4 zRR^4zbPW3>TdSy}mY%KG1d}w}1YtsQU zIaygvtzfb7erPep-e*|Qz@ihfz2F{qPK&-IW#Et3WVVBEAOAZx{>LZX8`*$;UUO7J z=Tys$%gZAWS29*p-M4XU^{&!Q2NJd*o{nF+b2^ouZ0E(~V)Y6uzYo7@TC24z>=Q5+ zm%e+L!t`0o{g*!R5Lpa1wCb>{-1U#gEjWTNV4$pdPPL8a+_{1Td-vRjZmiB@FREi? z(lo20Oth~qj3NB;5I3g|UsOoR{QYzQDyz&h7~&a%+{%`Pyv9{uKOP6hVIcIwu%a~J z>odJ2!9`GKbk|7(Zr&|$I|7E6l)4lo|y_c0j?oV!E z;|mco)`m0*HpP$lQ;d;y?CM+eABYp7l8YP(+Fb$J_Pa3#Y?h?iYUqhfZO8}9InCnK zTcN_F-b4$GEyeFDtv5Sko&erH44cW&uQoSE^G1-hJ*GQs`mOor9I|z5*gq|~{A?!B z8)Q>^-F}kauVk+t+*;VqBT`-}4VAg24cFirvY9BO+ZvJxt0+?vKITfl(HQJ`t9;7=|=Zncv+X-jjk zf^>hx%ywe{f=35~<~IG7GDRex@y6)VFaZ-FUK%;#i@j^$8 zW4jac)?_UH$@Ga^?q=rUYekcy_6BnhEwHN-p|5Ag*c7s+@7&m{o<-SeDYkViAvbE_t;Z}CcxwaU#bq*xBivf(jBjHq z<}*qk4)A8N-oqE{dt9hD^}Z_u=y;KCyZpW)#Z^S_Cvu&cnKxSKeFKvzRxs3uj~&Sy ze+q7G`$d6Et4MCY(ZKjdL1dx84U-StJV8K=bR(70;q64bqIA}JabousnZYn|zTEo0 z(tKG|Y$P9Q86mkjKHJUpc*T;l>u!hFb+ubavDfnb+2&z$`UW9~W8tp&i)}lK;;VyPkv%}!_=J-<}OC$YC0gs@)Y%Lxqy57!>5m(V0E?^PMp}}<_TMt=Ym>qVfeuARP zdH%Yofj?aS@?qt2+=D%A=pW&8R{Yo2d9f`ma^ALTKL9z66F#g-xC!|=5S+kkL&

LkB!-l}s8W%c-C$ctMQvQ#kl*ZUg_uFNyPRB$i6uDSWw3>|; zH@+hGiVB?m&5}~3P~yn2?lJ=&eBJSMch(Q~4-Frx&N8LVZ-?rR&)YqQZG-SUj|z+t|!kMo^6+a}!*{F-a=i*2Hpq2Eul*b_4 zTEeK9=WOH+>3pKQc?+pEE5?Tg@x$vXj_3n$d3`5y^QpUkq3}s}d9r*LMLKv)yLjhI z$kfGvG-0&@oEmqGP{-X3GxWp)K-=jH)ds+IhiwA)gjt^6&w2Edha3T61Bz$uWOE;EeI z$suN~!*8g;7BY)XCcZa}D@jR$d$*y%QO!JJ7-i7fG6)3Oin<#GF!ko^CsB*i^jugt}uyXmJNA%QAi;48)%c>$Jp@ov&%u1S%&4*k7=~d+!VzypA=Aee)!(ILT z3od9@^#7TZ1xfKfuB9p%jlJ zW6RT$#@}$vo#-kV9TMF@XZBs^7+gJX zYKH;hGQ3H!me4RA&!RyD8)P22meuJ8llXNmfTd+Q{!RGX>KMsXpP1~wBPwB_;0#X> z4K>ivBCeKO+>Q)Lf5jY?rH!j=zd~EmmBzR8{7wW;5x&rs5H9*&bZ@YgSsP_Oc#Smc z&w20;?;SPx1Xa2EL^rBQs!N=6xLT()60sr&l3&5=n$nheS z6NHieCk13<$jc(CEc3SXtj=u;CWYN^rYhD~3J9T-`$~%Qiboff!aU!<23r9Y()qtE z4&LdT7><>3_U>+Yx=k#ip9asrw7nCKumA2H3)v}|iac0OPsKQ@B+1`A1m3_nH4$5S z{Uou(KF>iB+WHU>rFurrqa)lqM&}4<*EeY$LrJJOn%UNd4vrR_kDD&L$Ar@fJD(dZ zPhpx<2EL^0)&4QiZ5np+d)+bX4)n=-l1rxvOUe%BP)kCE30kSrfRQB9dan4gOKg z<*A}1D<;mG6BG;FaoHrju<=DqTV{o=X;(xfr3hTZx;ZqZg2avd*pB3SK{T;yQoOG8 zH6?Yv^mTASS5TqVEbm`-Kj3Qhe%vt<;hJml9e8U8P@}7k65+o$^ZUmDEMyN$6rzZM zf9i98(DoySR#1`Q@o6motNW z;o0GYKc?4T{T1a`o!32yfS*iAD9`{BB0-`s3{pS9nvF*YDizB*l}4udw}~Cz0_MGW zS?;Z_lKJ;DR$MJDYv-e~t%9klDt<2YCRGt&K?G$`K_;YL&^wTTkRXzvOgjgMomXN( z1b@(==(Xqb_7`H)%dVw0?(nQ?{VcRElcT45kX&v@S@H%aIQsOzoCs|9HX zMa`9ma9NjGpI=e<5u)K_28~%Gy!p?K2DM#Yiw|T-#1Wv`5>q3QPHg3CeCNbOSYO=u zRQF6@MU<28zRGDm4-5=isx0#|-7{(Sh>=b^}`=3Q{Zth*EL#KLVao6`FFUh8H7)h^7S!l)Jwf?^ezOI<`l`lkDGnk>@0qpMDBVZN zXwl+aTzsK4WbcB;QgRj9C3ekBGYLlI{XXMq`f0k2%9hYSgBUEVMO|ha8Y07b(QOCX z?`WaEoJ8UDBWx~X+SJgT@gyhiFSl{kJgIDhJs##LtN&R)*@#G3^WrtNr8Jh` z1Xn29SJ=cZ-$M)~Ji=cPcag+GBz9ASIjSC|&LMfEUq1JGJpJ=*ly$<_2Fw}a%d7Z^ z=`6w=cDpXKO^S+NXL#tw~6CUT{SKBf%+mPsN3z{Vx}w z`IoZ}Ha}^H`5$#9+NKUN#z!so*?8%C_g5vFwUxg1WwhDzfLkx`Bcqkg-s;QbO3ft! zB(n+%c)J+h0#1aYJNs6-k-YfP#7S+^2*;<4BuKXez z@Gg~$vtxxX70Zl;Z1TkwCW)w4K&!xiyAE-bLkWpAY_$z2K! zFF~_iGek%xJQ%zFx|?L2b7fM?GKPc`rbx}az=(z$%9M*#YV*Lg4jh)xbo>@_%^OuG zBEeQ!%@A}pUeo127GZJb+T7qdZL_9zwz8EXsj7O^ZP>^85Kd-RZuwKqfxXZ)uAMx& zGV@}Goj#64!k_y~(CPihRmV62R#>IagOp`n-mb9Rfn&KnoRp;g0a(EZ?*R1rJ`ro^ z9*ksIXHQbHGu`-NP60xmvN+lE=SPW&NnCeHv^sZOf?y9{t+y7nkr5JMC18q4%NE>Qdr(Z>o}@^U|0{{S!+4X6w@8 z(Lc-*2@gdsAJq8?h5UMDm=ELA!9(z?J~MDd%%>Uig8ffA7efz}fFT4ESW0sGA`X?9 z=nKt>i(luyPVU;jxj745W^VLwD(7U%ODiD`R}|k~vd`QDDwROu{e>w^Dlx1p`Rn4N z_d%+xq`f(O0C;d})4Ib{X9TIr%E}6hFP?icg^{DH1oE?lC8^9cJ~84Fe^@Fp50kWsY_qHK6Zh!saJDkh=D{ zP6BKMm!Fg)sRY2%`ZI+|0zMMH83QTtVFv5T^_ew=c-%gaF`ObEzx0HDOO#0~hPng%l1OV$jO@M+!E+doDhTn%?;( zcXb=HsZ4DQf~S#kHzUaOic1Ou3z4Gnj)mKYZ;D4f58xSl!!ADY8`>WD>vVsm@CL^H z6LIpSLbJ*hy1E?<&_?zhF%(`~Jrx^jV*o?vCz_)(9TxZ(po?B2UEKk+vsPCCmllx>$EVwJU1$&?5ZYN#P z24Z%vVkE3NyCPj?Zr7+wtONcW=UckNHBTHN7ofxbevXL+=Q_u+HZ5e zQ&xYh@v7;}9*{t{8-B=WtG*TEQC!!FyBs#2_Zf-myaCQ3Ur}|zcdf4Ecria+JuqsX zUzw@DZbyH^fFKF8q-jM~w@Hk>)oo|>KCNGNfX3#Dy;E0Sd8 zo1VO$Yct5)+3E?d_DFeXpL+CnAo(tR^AKebeP!m~_pdm<$*N}1T-3pcjxo9L^Vo7L zNIK#baG<`|?Mh@9M*!&qBaJ#yAEc*tb;8?zL#wfV1#f}n3cML|<>I$=4=rlSx(qMI zI-?{bhaT|XWJD?p#N@W(X*w)Qx>g?`uDQhhP@ckEh%I-%FFOMgm+$rgQyJXHSbyg% zPlxA!wDW-?cB$Fxj(D3oJDX}zuYI-jGQE*6_3p`lsWIIeZ(**zEJ{!DQhI%u=Ush-)}KO3v=g?7Z12==0k=P<;Citq3j)vA)U!7u8yg zp^!jWM5Ml<%Z2m&sd>3`Feh8OU>}42`mZf{7)0MO5rG=Z=?D$wK4N&kHrV(R8N7f; z_W`iuq@q@To@-qPRx*GK;wc7PokAAqCOb5tj!lq5rhr!B3F`DbzY!FjN3Ib0+(T=t zctJJPWQ#0SLvQJFr*8Ym%Mg)fY@I?G+hy>JB#nV1zXLbSE9pS#>?jTqb24hfmaf1+t3mp!QB%)fhj zWa82oIvRO_j?G*jUOZrSCKyb=ghsq{$MyVY7iz3SYv`R)tc|6m%fEQZ3H;$4OshTR z_-Trid_#Kj$=ja+MFN5=XIxq;HV)0U@LfuB4)A5&rT}tuH9RK`>-B1vIpWix+zMV{ zlN_8m1Gq+Y)W1#Am;dB5C>ADkv9hwv z^rlt{BNH5<=p@SifIYe1Wf&Ou5?qlVvl3ZLO$o4+b3F9n{DZ)n09~dt7!#y`_m{@; z+(wVbg)RJx<9nVE#qTSwB{h~r$0N6p$8Z6XROVsWy z=blH|T-<#C)xdmz_k8?QLt%NCL^}{oxD!`=`fW(^pN0RSR&{-Z?Z(EC2XD*X;MTF0sv%y zl77gz`|r@HL5L0X0nL~9^T}ntW`Xby=z~1r{PaUu{I90$4Qn>=2Gc%mv;$&#MpQK5`G=bbb3MAS7K4tB$u0?lQg zx~-(^8z%p_LX>Xkh{>Bz44gTR$IGqNBneem@*FoEq}fxeOD#ioc| z8L55w*5D&lQASpz_udTx_kj-5WoOhH>z0_DL8Y`7cj(HoXY%iK3~FVN}?;!Ir4$p|2!UO1KHvJNJ99wb7JwCpuca1Dr#d# z79lz{)&3jX>-S70ImW<;1So7#a3?OMCpVfaHcA#wCiKnD_u+`B+tK~B)*ecsdBFVK za@n~yp-d5|0M0JO;o-UPGMdY4zc^i zqc1*8fG2%2tI~F6sWXG}v#v)5M|irYB%CBD)e!!hF?*CT8}ZT{+fL zkh=Ua`Mh&#sVX8KgDgpvHLi}yI_uy{8`SgZ0~s(`dxN;Kwjk(=KCsBBLzH(WS5a0 z^@F=Z)L06LW7pM>YBPoWaB>@}w-rZpFI?}kzfRC&MMzrIMp@Ze!GUJ-%af^{B~vYf@Y3kZ+gth9S9f5%&b1tjjX{Nxu(?BoKhvnh!4h5mkxbLAdIv^vvI$) z4@|bGG;psD27eJNE4`O_U*Jc-sXNC)QR9T1k&&H8_3k(N8sD(siv^}p64q0xS@dmv zyu!FoG%9fUqo`p~X?S)EtqhDlEAqm{5)6^^noQ{H;;<>gM(aV6IF ze@ziGfPs`&R5WoeOD9JeCg^XRGKq5jO55UmK`B6&KAS>#ANz?ih9|6;4gSG136->t zGZw|+o+unuzSop58c*oVjHI3}J#DnMLjBzgJ>|f9EUbP+Q9!n5*Mzyl@?J@oXe^&yq`1lbuQx zU)HJkqm*xAW;^DqmayA=T==oHmdtKn?h`-@ z{okkYBB{T z5+M$``B%IWCvxe$2Qne}`E!38a=0sO*sRu)xcMRFM(UP0*D=^`s_=uSs=_L(KD4q> zN4CsdofP=I{pm_%b~Sc_HGsCLx+{W^^M`<|yH+g>Xge9jp!5|!2rFt6Y6*yGnevS{ z&DK;GRMr~zPdNdG)S~1J3j9bz%kmb8kq4(UpVhytf7rqw=l71Oho zDF^4Q_Sxfoceo9}_=2*ACtB+Ah~=GC){@nr_S2fN^;Cp$HI{h(>)1-E*N`%i!+GdR ztC=9Y;xoKe+djgv!B||!1L(P|OXG@WQ%uc$f#Fn^EH9j!`qj~}$~Qc=dva4tt=<MA1Z{;+aPAy)%)j>2gv5A(MDd9ulOmWQU+ zdN(Uf)uv*2rzJm*O52hg*7sIjtZ-2ZtD58Xh|bj-)?Po!hv^A>7d>8nK?{pY#-XsA z{DVsxM@Yd&W*1DU|^%RIpv$9{U^>;D$)EKgN$5Oh65UcWW)`eUYLYcq>_kf;xgJmLsy*cxy}M)h z8riqgqHv228f`(6yT-`LfACCerD|44N8xv~-dm#7!fC^EC#HD(f!m4##CSgIJ=mIY zhCRn{Vt@Z5NGG7*;eEiWb0SHta`0+9PcJTBd!xv|(MF?i%La`96K(VkZrcYxd{3d1 z;f>Y;9b*KoBfKU4B7?ja?0-Or|Ap)Lzk?7VIaP1n1fXOe&kO4$Q@L#2yU3}1r%Jgv z_=vI6{nXwEjOT9FC+%XS%79nLnc~zpZNjAs15%LZx=@i%%^+91L*NB?4*K45u&6U( zGG%^9&Pb>x9p|>5`haSV8`&D!dec5sdk9D9T^D1BJOP`|ANrdrL&nRAGFw1q`DX%~ za~IUMJMtiQ7Y3WGsq3C?g4NPR@2H;K^OMW}ew=LiPeG>d9qa#?&jC&@Ou1P7sH~WQdZ0w?@ z{w(%v38Hd;-*$(GgMe7%4%*(;RGwzcpXV7!WG}17xfRA71`zC^1rNTuk0H7mO!%0G zZa>4pW$*Q|I~pmW!$Vl2mQOvm$!mKqEfI|poZZ?rdSKlny@c3KZyfA;Iz_$aZV>8s z8hylEqB#^`7FMT5F30yGoUs579N7Un92J$1s&H6lz5=_Xx$D}Oa>uoTw3RX zLNZwAI3m-{Xeh6=KmhE5f{`h2UOp9#H^3*<)LDVn5SYh;zUO}kwp?GPw^WVDe(D^O zY!arom~q$oD6&G*!ebSK7*|oD_t>k1p1@F0Z9?JtzMDXQ>YQH1HnP9i@-+u})J`s& z>luMlzFLA?aMfIEeGjT?vB41s^_2<%k`Xeqj^MUl$ z2A2M1uJc{%b^Ni@lgfNRJfCazme2V^-1Q=n)j?I9e)~&!2Y0vm@qkgRF-yUolGp7p zAA69(-mvWXkNc&$)u!g=XZymJC>+-<2tma^<)?z@TI#lk%E~u5?tK7+v2%&aT5^NR zjIJU}^T!!;3p z)M!uR?a~xeAEza$?FFg)8P~9ggxbUwpSE6YNy{!ek6+GddD+_cwWd7Id!))aFTYROYodZ#sr*2jTV^&jB^*dC25Ug~=&>i0KEls@3iF;lA1rY~Psu zff1i|)@M~LEy{nqBwWeyI1*=HlVKm28fJ87f%6r+q=em1njoYOycm1 zH6a|%D7+k_HPUl7g-0HXp+AfSgga7M5x!$zEG?f@3<_%Rb3(%Nq@L4`-?nc|m|xu+ zhgHcx$9*gh@jMtSUbB{lWzXK;! z_{@IEZ#2@EzC zKQuMZ-5HeRwQJF@rARGydBvIk*bMs~S3j$d#NqcuYKUMw*mBdUg|EfHej-JQKa{OCUjlSoe#`GCt;XIhR>%HON%*`1@w&S12?NPpO!P91Yqsd*zkpJiYbC`5m6;(019Qg{ zy9JpYnHsA(Tr5v#_!w1zdL$!;BM8H(W-V|Z-+>Nu_U5=^Oxuv&J87>#!iFIo9_2ia z&M3$SO2K|+S?g>VzqK|%KvBomuW+Jmo zLX1yYUqh1DV~-NE7>ptVs1gr10ylkX5t^4IALi||HSYma(9XZ0sUD_%l4 zgpfy+YE+s^l&Fq~9~tH`yEP2FL$cdMam}S-<8Ke7csu(CDy)I~VOBwYByI;f$~?8g z+AO?-MVXj?(knjyi#alD3<(aM*S1OfLKSGV}f^bQ`_pnzwr7U3V z;(TXp_o}g35$+DBYqm0unHi$K{RRPkcsb%NYv3D`q_%(`5K-|`UmbWERItzTx=c6d z`2ee!lb|Uoh6%pig67-%Ijsu#ZbiT+gsW z(7Bve~|9I*Q(J9fZ!ZdS0&d&_A4j9YbKIeZpBl&R%cPBSFX)`$Qc`1merSmS*a zsl6aK-bm`{hR!T(O_jSmAg^*{+(>_b8xyXbfL!$$l54N2`IQNLP<=dvZGXsA?DR=q zgqAJ zQ@&c-HOUisR$bS*q3(G3K?e&mcHv1w@+J|)DR5hc45g|N@ysp_a!$8mz*uYf&q39j^2 zIi^=oYa(<(O=)H>O`SP#=m0Ta7n@kd6QD`2-jrNlQDrYFx1BTPHyAu%x}+*&@Kf=? z$v?(&78G%q8T7DY%{DffP#EDiK2Ume0z?B zMKUOa+_9RnPgR%k-iA5`zfM6p&fc~`I*BbQ{x*VcNVX=&Z=5$ek5c<*(kNq@eUXLh zqed&|`s?G&WS@;$lly=(7rNOQzFJO04=T4v1|$?haue*B0)pAV2jLEJVn<4M_an1YtK0;!XPzOg!&!Qw}Ar0+ex9tAmC3 z+mq|#K+B^&h6XM5@#PoXc5+V=za#e$SH_kzhGi5--0#Yi;ogFcnk;@K^`0bV#>i$h z?}_OY?&x2z9*!D^JDrY;O$66o_{XB|EQh0_b{an)spns|(%qEU4Z--h+C_TK$?z|d zq`#`^$ee~|BMhx}^2WZqmI{A-GCqYXy^c<}Y##G?6w-yMI$}xY_NB$DO{7}4im?e- zr?}$LHnukUca@eFe^_jbk3XT~J&7aO`*w!F#z}`J{pFnqBN80%?jJW@(BezXDKoLJy5h%& zDhT*_-hT8GCV)h>Yq;*=>N|ktdyV=t^@jWDR4zBme`V4lh}jXjEm4j%*K=yK+3M<5 zDsywr-~!S8i;50`^zC(Np%tr{fD6#WP|g zf4IP?4W*%x)MnIg=8z{1VJ5@H5U}xT-98`eR=Au6YnC|1_X&Ci&xTex?>4u;vf+3g z>3*%n6U`na(Oeuvg%7=QvY0`yFZ_mbQlgy!^1AcT=>A() zZdG@;2P8ow7?em{8_R1;g~-nnkrJ3{OMN^I`Lxmzxh4!JoOC)Wr(sa_LK_AO>$T|y zgh(P;Sm)Fc)TE0MNuR0$+&Iy>+596X-4stxj<1OfjP{R+UNJ+$>b z<35g0mDMny8I$_f1pnW}^|b4T)`spqM?9S`1ki#D(2TLpTJBF+KfN{PBxgh;T`f|b zD_W?f)vk_?6K&cg)!uUxy{+`0a|*tyI`cJn+Al36NiM$S zD3Rme((li_v;+a$psKtPO^hk@M<6H-A^7ZPpa9B=j}5u~#!~W<|AS<<)jp;Ih?A2D zIe+#)mBuX(Su9LgpHCRu3)gF~of$L&nx4&*wM{DW{X1_MeMT3bSI!{W`V&%-v4a0P z3C)~J1Zqm|hc$NiEh$%xUqJyU2nvhk-FR_2j;A-7in@%%)3$UTMj9$)+A5owy# zHFIEda%e}`ZbGQCrn0)7UsiT903iE41V#LLN}bDoC0Mxyxk}h|PDA6S&_>DWU~1}B z&3-`5#MVpI#C@rtuJ%At&+ek){ufQyP}z^Y1ne&p&CQ%pSPG;h=ava%wjHurfN{FHd~QF5IjL=RrdYD z6e!;uGa`vdj3{kIQ^;8!wI$X(z4tE^P610U^c1{*>x&?e&lY-*%NizGg)p*OrC-R?}o3C>qIm>pYojPt*k=&o%KY-3U*r{=csAd_&l%vbr4#{%Jny-IVFqjvSV8M2Xf6K z?FURvO?Nvd`263AsOj7pl@n0FprM_L-F|y;RFB(@-|FpE zrN5f&2}V>Ae+=(h+bOdu-0Xj)ZNP|0OlwFC&$extYMKzQ197cYFF=s^a}+G^iO+!>nT5nqNN7(g~ma)t8KPYh0gJ&e-AFmeAreN@E+ zN3;XRZErtn{kvit$!(IkyEg{j*Fd)W+tG7m!Fe}i0}G`X#8NthY!L2v(&GLP)!h>S zi?qQSsj45UXZR|r%m7qoMgdnKS+5NOpXwpy(Kd3{}kgar!{sr{%AlCC2cd zvar$rU9{D#Fr9N(d_p9~0Y5%IKK+me1a&<{m;1w06>jlCrT`>~k^D=@{vU;E2~Lm> z_^-KlXkTOBu42_pKS(p|Y6`t!6*K@N-mWH?CfN^Dfw#IHl6tfjF2D=G)XfS^=FI}| z30irO=TA*9E+OzFvnp7$K*yVGW&k-ii>ubF(bs6=m1^SQiSE3`3_D)&Rp9MW3r)G> z*&A@evf{Fp`I@oeWm8ru2Wo4K13mr?@$&44JgK47%fwY)ilDi-x+EK@T2<>@j6)$e z^nl{>+InM_-859Y4DWz~6eHmM*5_(gg@fr6s;`r^fhKQ_f&Gh^su#n?$Ifq5-U5{A z3?rFa(GO_57?J)2@(#DW^EHX@14Cd9jhHiax=xK=^dAz$}OormeI=ZYpywe5qv+#=myklQuy%cVZLgZo|IF;ES-5Mjc0ei4V$pmp_p zrL$N6J*cCC>c!Ixlc4%-g3mkWN zbv!F$#QTrJXaygs-2)UE$eA!wI4utmvZ0l1`h;vAm|g=;B%SQ*-tUx$c1|o(o8bC( z52Ld4c2BIwE;q$tpX9l(LHH;nYbGciBtOP7NS#G&LKTk@zMG~Y%|1I^Z^=?E8Lt1G z8&npDQa?U^Ho9++g(3*1RiMEVaMAd-L9`oRGO_qh(2yTB@`02&d?PN`=MX)nNjaql z%4HWxQ>1ITN7Yxk6>>H)XbOP%%0s`{=whZF9zXV^oHY}Hw2kR2~$ z9<SE2H>JG}XNW$u}D#os8&ZBvxp(^1cT z+M$eeOho3tu?_8{e}xv)EXMz|1^>rklmE}uz`vv$|L+2M!vAj!{@)mYjOhQ5^I%$m z&vGy%!;k!ko0g7_Z%P>i@t^!_!|}M%4|)D>Ca+0fQ|k6lW`KIdVdO8HKq9zI$)3M-0Dq`Mrglwuf)N9?s1d%&epXn)=LZ!V6?-*0+~1v7CS;Qe?% z$iV;H(LSJz=gu8crl<8(j`w)nBzBkyTkeSWo#m~=<>v_c>)kszxqSR5I5EZAm3z(v zJ~2t5uD1nXy)HaFo~^U6!6m!Bgv${Lh5cHeeAKy!^=+ z{~HwaVK67p5PNH0Rig$Do2LMb(+a#g+3n_Fbvyb`Zix8Bi=^Ng zZaW80YOj08j-FW2?cU4_HD&5#jXK7MRFg(8=fug*<^2?#2483|;=RcECgKIybtm_cV7jmzGuE;N z_h<|TaG(J+uBbn|wRPtt7}!u`Mk(l6q5I4Setva7!mV$68XhPg+3$dE`>}e5Mq^sM zdk3flr3Tg|uaRUap_V&oVfwRuTgg)QsP*)=fLV{20g^V-7r7<4zn(3P?;LbYx^huE zrsEw?zdbtw z;9Z}_Kw6!)xGsI>6Y+HV~2lr7{fQtZ1&Ft{yZwokfp_UfYU>@0Q~4FoH*mih|$y2 z*w7g$#e+-oItM{Hq#9E|)w*SFJjM+c%gd-Nj?Q_}Qm1XJ5ThT%Bba@&hwpMHXKpb)f)Tm#acywgiC%cXxcrXR6H9Sp!hDg|5LJ(!4A)1 zDoA)*KjVI}m+SRv_FrP#M{$QU#Sk9goU=p|#>&MqMerX}AOEEUo^K1i<3yTn0extV z-1%j0MUBDMVm%dNJv$8hRP&&P+}{)oAJc2qK6q5NJe3m(_<$k^s;Xn&MZ#)LBu(}3 z`y>Tm<&BGJq9GdaUVHpixFn@LtZFdQLEy(n4 zLqme<9>pZauaWPX0+fqBofMeD%=%Jfa1dEgtz}QzF0yyO8s^D@Y2kM*0VAMc?5h$S z+@HsKGhXsN%{aKWh;aLf4a|ZX4W1V*OLF8@4Sr0*v$=FecUJhzS&W@`E`H!P-!D9F z-d`t_!y@`3Kh=8I1go+$A{IMVh&Ly^ME$xiZaoWWt2E1S(^1_*#wj%_y`OB ziwuAiO8LST`vR}WXOX9OUsX91CC8RVaLnv?M8f>&wScIC#dbJ_Hef^-YNj+7nc&HC z=Q6R-x5h97vV4!d@VB{o;&gi9x>uNj5z%aM5WuG2d9!4*fI1FH^hYV9dBquZ zW?NI3-h_fdjwc>GmL*AU3OTwD&?Vv39d@2Y&))&rAn!MZy&eAZsvpdP-;)@ryBD(_ zWVJOL+C7zSSh{BjgJ+A6;sUdz69Yg~pw@r!cL=o(_8b2PgQJeG^JfMP#i7{vOG)=s zlUEbYY`347;`Hfw2DXDW>SB6-uIvARLF$K=2Iz5x$nXgBmPdpWH`+^yhK+z64G(OA zw4mlYqr6Y_#%S&_HB}sKT7fAosfg~-R};`-2sngtr_;T-NJ%I;Q#OW|V%6z`gxXV- zgw$N>QBNZzg6<+j(3gu z)e~kuo#I$WklzGDg>iMx&jdBmbuwDe*aim{luSi{W+82CT`cn34IKetX4hf`L#dX0 zQFewoUGEA@bT(GDpFSEtHN90i<8tg9T9crg7gJ@xVqNb1Dv&cx`j5LyqJpm2_&KzJ z2vnGRK%mPDnAt!`z)9bfoJZl+Gp-FSJyL$+Os-2H^S238lYdLuci6`S!@aNd>}Zyp zzvc;aIF2X(&-W^5Is&BH_5mo{&ZqN5LK}xD?-Pk+*cS3qm+Mw?0Ce&;uQWm1kz^3@ z`!}|bM=5lFe*(IyPSg4!80b~dtWKk~wRO@I_rjh#s7FLchmbS1#u^pl{g$TRaaBo= z*CSrHwr(G0enDNw)c3|3qfQ6hiWp{CPmH9_sB~JpWa)#wKX6-qJ zWfv}C&Uxzsmxv1VX4rFQ4o#A{StFOsBXV-G+F&gXDmdxxaxQKm!}J(tuui+as`1&Z zz`XL*F_mj+O&yJudH=*@X>wF?Auv6>f>oNpbw_oZx-b%Y@4(f7!&yj?GZSURRE_-- z<}{pni>>UrCx6U%0rmRYiA13$>mw;GBA(9EFQETgf)ZYZMZlo=Onqs%eu153K(bYnMXLf0)!_Wlm)9|b zYDzw$hir7g2=xY4nBZB)1Y4!ybRjHrB6$n=neQVHYT9hMxuUo|@>Pd!A&V_9z4n(C z!&`PfpH4~e0b@srp`~s-*$O$)sgKNd$J`%~81(k> zLUtQnw%iXOZY^f;_kvnkjf#^Vq-tI@?(dqbVrx7Fl~oJDcs+^HyQ;=`#B3PwMSb=X2?p@A@pYrSYY;_eQR8{Iy0&qY_HhO^?5 zx7SjY=+UvY@kRM8d1;jvMs!CTDT6qe!($BK=JG2GA|9f^Y>cQ@BNlUciAE@Y7K88J zF&oPoXPC!FA5xr~&?dxe<3`{KOorwh$n(}XIZfgKKnHyDjqsiarWsf2H!{o%#xtTy z-<)6c-!}M;HZ$bDf|5~R5R7ed!j@^ED<<@yb&Qu+$)NtV78@bFxQ2#gmN)zfl+K$~ z#eQZ7I;k+r+Z#`65wA8WGlg_-LO0L3yLFa9ibgA?A-(6kQiHqRs64I6{q@asYCMf$ zG_h1HM{C^leQKE5<`B>OiY7-}^Zrok!6{d8y6G2^MM^Zfo^_2i7n<9V<%OE%49 z282g`>qFY?2pRn!bCZjbcU<+S>3W+Hi0x`WsD+?^|Hib;H_~7! zXQ!ug{FfHMD?0h&yla{=E?E7dYI{Q&=eCdzZJIXyGZlw!pQ`CVOmwJvok2~@mMWz( z`+zl-pcJGO((^r`?@HLCYKn|%)>ZyR8`ReZ34>~|pGYkYy#~nVlAZZ4wmpR#_N{NP zcBuu*L}0?tT7X+IyPZ*yr6OJ^KKZ#$&?Oi_Dg)W3%;!{t0fdb_+{b- zccfx99J2w&A&bgR7EOrKqhzIrVB2yQr5ztT+7{i%yx z0R!nPZ&=m035_Za%K+u#3R${a9jixV&yWS^c&~z~Y&y0+Pkz=F)L5gwoIk<%5?FsUhPJh4x(6QT&@(ftY#SkMg|K3yjd3s@)|P&wE<%|EvjWP z_nx?Z&yPx9W-d1KEk}E)+?iW*sU*_Cr5HQ*+_%pV)0RgvU2zu_Ege`DhnIi#+&))*X?}RNU$&E-_OR(jJ|+D z=9?WIL^#|N40b&xviLv7t(M&ZF*4|pz;SJe(N;8 zr+dL!YBStCugC*Sf944O47^4ru5b(z6060^*i6_$bN<^-Z> zA~NT>y$^q|^v~tKFTzP&S7Ob$YWr33$89z#=EGu0Ig2f*KF(_Ir-b~Qg@mxGj}C2= zH%~QZm|z`PdLZva z@mVE>AjwBzM%$@pST}tb5*i{@>*fB06{(xSFhXkdAFsx2nykL86Qgimj~S;Txn?R= z)KX9zsaeh&)6kOTqOP~rYo__uBJcwSa-gn77~#p#^7T9(xx|dG4g7%23ch6$BJt|A zDQJ5{DS-&BMt!U<(INZ0*@w%40GM5u_u6DBQ&)?SVz~8C1jPxD+xbs8Q^`W9482(f zTIytvDt$y~FB+Nb+6Ls^t6-g4aT4nZ()G1ySBa6HyFwIRPe^0}_vqx*(K{XYF(Ak(|b?I>t&byj3( zp*xC~Ftf|!lPBMrS_`{ZU~EbNxx2(z?@hQj!Ev$@wll*%_ES(ueKE1TLbHBePupLWPt?;w@pQH<0G-B^b5U| z^=zHSwf;R7QE{gO4^1>!V{l}|{8lEdXN#EWLh=^n)BCqYcwDU#-#}0|cFefPxJ6{` zACUDUF~ff%-{gNo!T*kk|EKq$RFC2UGe7G<==6nAhlJEB?03!naGvb@O-#%Fdtf_< zN05Z#C>8U?wwxUoQAbDTpBQ|qc`KyIE*A4H4RLasga22HLb=|3kl7;p*dhB94rRos z!F@_@5w)rHhZUZo&u_tDJL~%ZFE?r^_kLrYbd2z&qjE(5v6Q|^C#%g53EDSF)zc0H zx#bz;*@*Gb>kK3h4qbbZt8G_KBNSY3{Sqn|O%Z>0$fUJR@mh)pK|&c5W%P^lyPO#Y z9FI@&fry>92|cM!b{p{NriDYoIJiSa`+Tkj0FvPbaB6?SnqPxI_%O{sf&HBS)V|yp zgB@G<>ar{DMk_ zV#8!)l1Mp9pcRCMMV@y{LVoG?kgb+hm)vdHl{f^r!2w$Ftiftd^d+&);m$38O>%t8 z7`irau@u5js($CP#E~56+ivyu|6W)@R1jCJx$&c}Riw>v{36C%f}fu?(`(YtMB+6 zmAaZguO%}w@P1!5d2uQ_mmzV*Vr~kp5hb2RI0Ifdl&xc(dJxGphMX z@zdt=-Y1>oq|UTC8T7i-?x^UOqs?M4$RxD-s-sfFYukL;-VW?#-rrj8GHpxGf`(_# z>j=qL-07iqJSSjx3m@wxFDTlwLVw7*;u7hP`uV#2%-UR3n78~)ys-6oGD*#4_D^O2 ze7#-)fmp_o{8Nq!Il78cRDt%~o%sixtH`NGb$c2cus(oC$CcQ;zUCHjv8^RJr+wS@?WiF zu-65oZnBJ_iQ?o%!K-(^KO5O+05!c?6VJ#?`!DM0%3B0`quL2B9U#kaPS+YE9+g$n zMnVrNS4EKahK2-Bld$BM9UtZYF4M;73f@n1ZX9A`e6n){Mi{NF#7vce6}168yNlnv zR@RSD(sQ%A?y94FZ?eG6#6#>nGTxa`+gmNtXPm)T zpbFpRTYs#$-x{&1z9Xl`c&<#HSOV}P* zOJ_8&pW~mL%5`OK`!J7E5_HjsM8#GN)@!1AJtiNH0tFXDpC`X~yyD64NK2@UJ`3*p zz3qvOtn>2#NX``iQH>s*d?;_-@D95+=(9?)s}9&?DC(r;_AcO)(Tkv+;|CAynfs~ zen5%54P=njrQ#vd(!?>ft)i$?J^1>*Cv88R(L&?Y?X6Sk?k7X9D7i98F@PQ_zr1cd zcbUT$(FAI~4Nnm%*grY!y_tA<$F!UExil1&`1 z=hQ?^pVzw2E+b_JI&SnwVQ?e~wKq54TYuBfq; z^4sz8&@wM)b}{NR*XB932gXEr`?0QB*seu|;Yky6-#rj&t;4i!>}QxYQ+LXl0EGtx zzKyS~vDRbdjXe{|AgO;v1|fxF5?qb+=aR{8wek+Q6q&r+- z>yf)J8VqAfTgiWOKN(O4MsS?P(uq7cY>&2AN;48DYs6Vnma=b^zciu08|BBB=3#O6 zSWDTQ!A5sGigy+{%1o|xhgmk!TRyy}M5}dDKYuxJ28d73s8Oo|B6buz4qV<|_R2Jx zd~7`Vf4|kC#=@C;9JyEe&RR|-(fg$IoHJZ4`n7zrscKa|%G)npggg7r`jw^B$-;?i z6Q_!np!i$=RU-wAP^5(y58|c>v^TPs0%vSOjQk1` z2Zg8vxn~`u{pL<9`-`pQEpkp>4eXjkfI3ih5y5(*M9pk!I`xC>`QFEcm(u1zWp zU=R{KP3VW3Q)SIb>O%4DAqNQqV>)QiWbfb zS z|LXtc8+6#11+0%CzGX9AJ!SpgJDS8u{i2n&sE9={=Ezw+p*0>i$EIr0ta@1T8!&y( zF?+GQ$kfLj*pP@Aq>q;nOIbgzD{B$pRX88l8?zgWkJ>SA%jzW{$guh+@8H||*mB6@ zSmwk*{Hh273_*`jx91-!Ym+f4JjQfCkgdeSC_N6GZiN>+A2mL~<0vge{N2@8_k%UjbUuRdeoIBW;nMb=J)fl>vy`S1oEVktKhvMGn?LB_WncHx8bZ3nh z#l`--K4|WMO(9qcScBw>CNI5M*WGOTeQzagOIma+-~LEws(`5}S=p`zS@l-7QgSPk zj-a~r%&#ixvN+H0Y(=n8k-0nQ$vW!H@!$H3HLiiwD$ z8a4ICx8d2me!LNVI+HRDP{9OhdCRzc-da`o`Rx==P;*U+3+nP?G3LI8U#$%CK|Eo) z6`cl9EA&}YyqqA?Kf6ItLv?3l_Vp^!$hk6A)%Jr19pa9=W7qVE>vjTvqaGy|?x`Ni zfrm1JD@E>GU#qj(5cU+S)(h|E%4($Q%nKM~dE#eih&-Ne!XmygsxsjpB#fNvgMqO! zr&^ieE6N72tQHgkezg6;sxW=I9OY{hF{)!HpT{$X26mjRwP?=nOCR!*_Yl;PE+MH^ zWu67YyF7uPDRD}|L+{i$C2P|@OvkWI-Z$YWR@2z|m^XRSevkrh0>BZp3Uo&IC(2@8 zT5d=aT54S1s6{0rp9$Co(6+F&a6a_zXhFp{6erjL`-`<0v^Cv@B>}2av=e(n!@p7g znz6Q)d#tIYi0h@)5$P7Tm=J<=IZ;{8-P;fZRt5aMRI*7xMS@Dj5!SSCtC+hCU$jU+ z)0}+N$9V|z#i;3wPmy;b$eYq88@~8q-Z)~xiZaW^56C2sm{cpA_>|in4WCx6!2%0} zl_4O`;rpGKk(85DE#~yQj^{8pjKSdCJk0B zB4e*kU=F#0qnz@!^(sw`D$D?N!z7I7+-}zJSI{?|GlNmD?RmX@b3@`1PZb6(UQaN; z?sPszrgYup{+c@S!{PCS+R`@12lqtOx;mLmxP2QndToUq&3xb`0#?Fw@M-i^CL1=i zrVBHHDe`_~r#i_?Cgq1c{q(DANB{DS`S6Od8w7GwsB%vBVfWtLTW* zqai)B!ChZ-+i|aIu}Ri#&oMAFe9I)7LtJTH>L6$mDpXnV#0r&u+Gief7ZmQqPbR z#_v?QBzJfkEq?p?$JT;to_Bqe(dUn3I_{CMj1r4>UZhqtisTs%`+YvNAtwoc03o)m zKm}|#&e0&w>v>*ffIqjHfYOEnnSPfYDZa#aBncLGvmri-%JSC~?KBRx1JtfmYbS#j zI@#gY`hxw(a_EGsK$DN3cJ^6|Aivv>Ch0FkKZ+r#Qf;d_v?HW62TLpd!Cd~6*Zaq1 zZEt=Yw75#sEM>p18nbTQB%t{s{*ThWDyprh-44vLbVW=33 z%|D}Leo_=(m4SZ#!jB$UQv!(h`w(C0a6k~#r)!rFl2PcsVkJ3ywxUgM1R7r!qzaS- z1a9(cxql=(U2AJ46U*9-!+f9s%gYOmHG zN(Ua+Cu4Z1{rQvm)8m@f(zy1_~?kbxtnoix0(4{t5?<}jHnm^K-#?4X}5=h zoJ){N>KUR0EXgrB?~ULjzAdDZ7+(?k)lca)j`_1qFw(N zb&&Hn7Mq(;+@MU6k4ahjpQ^wv!=~@9kqk|QdxCcGm7Py1DAJ}2Zmt2y1heTEFM7Sn z{X{Dd>O(s+Et49*_`I-yl26T~d2gwW!wO1PlTw%c7Dv>C-Dc%1S`K08`M(5b{GET@ zMQ5TqiG3rnyQjy1BYaNVPl}~Tp4KN|BBI(Ra@A>t@*kr$dEBS6$bgZEDjRWY90;MF z)+@&Sj>S&XZD12wNhB_{y__3stGM^~Bgj}{ncVh(A^sDg{u}xaQ3EWW;wtsFQfq@O z&j+J`s&-Z(GfH2wD&_v>*oJ1i(^z(X#Uv->w#2}3mC*hVU8uQysxe>MTnxZBEzaWi z-iADC{-^O8H)Zzb&aRLqkc`(+}^0MW^ z6bUysHv~+PPy5_lb*0zj03F&Luxz*ccqfX8gaokm{m+S|UrExEki4c1@H{d%ccK1) z?L+|r3k!>fhsWCbdcbm3e6}C18gtn!&nq%QUKJ|rHmge={Ld2$zH0SBmRy{~V%e8x zGh|f)bTG#TK0b~C0^QqbbALNJtDIh6_bo3!NrqCq9_u*#JQrS~sDqrv{ro{C?1O}* zZ1IYog?D=gp{r`g_EzlU>8A8K>y*4E4t)fpSNp8%NuUl9gfcXNrX@73y2bm|mH-?+)*@if9(yPA1N_3PU9cR1NW&fwe z_n!d$$+5YhayHCFG4EG_X z2c6yf;&(0Af&_D$Txom}j*gF#e@95DGFf2KCK`48?mz5zt;5_2*FJzwSV+ogOg4LHQ$l)46F0{)nt#bl1BqeLg}(EVIfIZvXMm)5@%^s-+Q4^`{A*yo zxonTFs63cU)(d9wz>f{NPq@ER8u5mkin^WBIO~HZumyerr9jLyyreY-msux& zWeU6Oj#_z|Vo*VBOL^zS=;1dOHm5DafA&5h;6p}OpyiQ3($AmlhFcfG!YDgE%M}LA z0X@27o>pCE^xzPk!FWGZAg#x6wV1a}DJ_rf@P@dc5Nuib{qoWhgO9R!9HJ^$)wGg$Tz(qaqzato z!*Kv&uj%>lid+avc+OF&E;f*WZZnofAKmM| zANTmYCyqTW_t;0>WB*=)?2kWf5e6?;rnG&(@h~tzJ(iS?fTk$trVYWn1NNa1=5VTo zUL0Nt7B3CFfIpgjZK*q71e(Vx91xTRm;nDB^>*S}r?nP++a zJ!jg1Ky11-5)5A>&B+4^)fSMZL?4TL|JR!Lo~t*_ zqB4j!e^yZPZR;JBK$ejZvWFK;$Wvq(pM^vNF)il>@ogfLTEYffG)l4k%`mmGq^~HL z2sxZAJtN!ZpPiKLevk-SIB(9Lt6|NciNr>1<)D^DB(HUyiU(KB=(VYOb2IY+Evng5 zkI0gx@6E>#HwYBY-R>D3npGTiOIA7)Gj+czDjv0kWy@-w@6`wJm1cEX95w2Yxomky z>|-`F*znjl;_Pn8ctq59?Uv%}LQAHwupI*vV9_q-^hi!+o?_Yv%ie-=3noGbiZrpM zDpiX9k?qVH>g+ki-Z~pka7AMhPZA3|o8ypmn=s-ZnZd?hagq`V;kGG38~L=MFm7Qp zlNyqPfg`4jh5&4-<&$Xi=n$*!ol}TH4)3-QAFq;s4w3#tt#Yi=(uqP#!IR|QQc+!( z%4RkD&8B7(25a~3?9)k;_(xfxUN!0;(}`$b5|UgX4@%>2N8;_cP%)a!upf3Q`#}rw zdb+y0R{Ga;aJ#cK;;Caq@>Bh4z{v4p(m7Ic4LwiRYrEV;%+WT zxq35m{YioJqU8B<=VdYREhM4{czjH2-Fg*EiGDVDyT0fR zCahJHX+^y!}xI|JQl;6t=zKdXyN~;n{KxxTp*&9pMU>~IypPnEZRTH zW*)lzURu%x$G@^pVXLr^Kua{ts>BMTxw*NiwRN(-l;6(I&f;_0!HSI6`d?ULR5-WR z%!^$lYUtPB>^X^erKZ+{|B6a~c*Aw}BOvKK6MKG5;B`!xC>r&%j`bn4!SWw>i!1C zn8rB20cx>vK^4~a%0R7OtkBe^SB-3(d3xZ!5Fc*#q;dAl)T508(quIkwN;OWLtrN# zRQ2^Mr_gnR)Ljol)}6ID?caBp0&sf2?A>a$)omE7S89*&1LV!Vw3aU)4DQd2yWcMz zTnjopdzFVc5}yFP!lCb5!lmD4{8<;}`HK0T^gFUe+#ezpb)#C<+Ca*5z)E&E@NNe& zJ3Ty$Gu(nY*zyFevqNNG6dvt@{0CTcD25S_(GSsB+z^pSUq+%duUBBQ|G3^P#BDV~2Rd zZxU1B*fcBk^ZUt2B8K)ND?MNzo=wd?bK?-FbX>HkX;^Yjl8VI4Ixfg_PQ)m2CS5HW0% z$sf6wpwLrvMt*r_8SHKyV>PgG8*qup>;=IbVyFZz&0GjI{&CcJ&SM(I{yy~nv)4R? zzZbcI`R4)D2PIKp=M%u)m`touxC}+3fFC@*hyu$GeweNGa23#4YSbW1j|-ECqbtXY zUojRC3)Iea=D@m`h^!FSn&ef3e}3Q>*r`qT=p}o>^-8m5?(wY;yOJ(MgYprDClk*2 z7DU8gC?8I3C4o#|7lN~2$j$Q46HCS#9>U}L zbxapd6m% z#V`K8Ij|2UQwmWjVHe#S95{w?dg?)O50rxY<`Y7h%}Q$(dsh~3r5Gf96d7|NKd5H2 z1U2|SD`&41-%K&0i_L`v0N3*gyz0%c(Em{mP{K_1u5lG=-)|939k|ydB z649~vUO%0-$*3!1b|DD=jXQc5uk}=gI8OcTg*y$3K2N_D3=uxB6Qyztc=G2{dAR|A2J?;H?mb z4GG;nzd!9b6vB=s;9##S?yhi$=e?u=*p*hSlo?O{j3VC{vB}f-h-=b?U0r|K;>6=| z0yjmQ7zxh_IAhlE@&(e4Y^RR9UYjbl(PQX)0v)+M2C5a}_3dUnEu0vBeb0jfH-GcB>PXGN_p}as%>PU|r z#s2)6IJ69az=x;l5SL-+Y20+-7f#f2l*QzYgD&%(`{|fNlrZboycT`Q9y}>$)~}DO zat|OOp3=PIM?t2Hyic}T_`J^9#_~UtKxrLFEn^r_7771`O2@Z&zN{J(W9UU6O3U|X zM;=rYkl54iKBEk8Kqa|dzX_yE=x3a0_xKE?QcwuxRm4~+lw!9jM>H$ zNY>>|V2HqW56)q+A!$zLV#rVunG^j5yfcvmILCfr`y1V_)j6&z^qsLW;kk|gf@&IZ zI}5064{0$*sJoZCPNcPE`xeVQa}^gGU)lWZ;4-g^nDaTYKpTJyABFuUy^7U;wgCc7 z7tj?62m)OEDpP)Z7IP+}g%og^jjVdQ+Jc|W(r?){6dAJ-jmgUe?A$82#vsX$i|imy zcqhlMs(iSy0ZSnWiya8R7NhR(&PLwBOT3h&sa$16<7UsmXUqnj7Vj(Qt{-V1D-?YD zkdvWEzSC~1L6ysyd67ncE5sK4j}({7k63oVv@mbsa4*_-Z*dy0-G~~Jbcw5d6$LaN zr^Tc{wc+>#zOqCg6sr#n2L38yG5Ve=?;dig%6p3*qfzHXqjGX2ncDYHu^_m@c;{YU1f}Nnd`f9)*I5#0=BFVrLY7NP1knpS*Cpnb`I^-kV3U-B(u! z=nwBtr_bFP)0mI)>vX&ZiNSQ`>y2Uh8i-zs%q=;NZ5Kaow-s@|-b}%Gs@ZFYR2_T` z!N=5W;c1`XgCQjqOU~wI6D&PvX?@ps*@Jl@lZrbR8Bv|BkvoTK1#ay1mrV`c_eoXP z?(ZecR8zA-xtQ57bHivrG7cxGn5xenCsX>UY*K8@HVs=b0>hvM#%(5(x z5BpN+*RiTivetwO;BfF@i??B6ut4#rx+?ZkgTHzxDx>)zc12c$;q?AkGgMjXo&?hO zJ*W^l5|M&<`N3z4yqUi59oS~WH5!Vcj+f5jZorzW)r&A*&t~Mw1y3d#lFcjHf(6TkRW;Oq~0G*;Ay`Nd!v?C$cy2>Y+i-llyt!&f+&zNYvrqGkI1+K7)bt#&`IWpuaPI zeQNWjH@QU+qp4%t`e`PB!C0jxi&gY&D*wxXYd9-&*&2ML*VM=gy>4;sRP>#J&bz9`%yryZUf31bJMVhcEA}j^ z;AF^<>4q=&Z00ptW|9-WUQUSTLF=`CqbO4NxG_sP^vp0iFju}QaeAAa>d_irqQxky zQvf>7O1+Ji4O@ke5b}psl%sUA;Y(OyS)MJgT7*oP+ILhN_gD)l0g z?%W9*rpIiM3nbzyBsLrknCMY^3)Q9u$qKBih-7~5{7no(m^*IDh6W}weOm~|JR9N> zZ36+!iMg|HUIdHky?0E7-brCuEe@su7_9}bC|st*_O7d_uT`ld)013sGj(L-y`xy_vyc5G{^sylX7WY$U?Q0cS{0rw^Uf(k z-^!YYN8b2oqlZU8#vz`oOSjvyHTHO|E-ShK2s zs&m(7F5<^#=!@M(-Am$g=QGyKkzDI%N$lyr;ba?O(a*uZ(@~Z8W@EFSkEegXD!}2% z%f>8p#D9|}c$(|7A+8mveB*2HiTLs5m~^LI(@d_|E2D>ue4}tXI13K+EFouLDJriG z(iz7J^Ef6Ub%H+BN@)aitFj<``&RFDnbmT>tbdgoj5dV<+}ptKK&){k9r(KXUPxP8 z>qZ22wVK_9wvo(!Qq`Ape%VqJ2Qr}<% zUI(?vHarC}(9s?GoAexRxBmb}+uez~FCqc1X6vDtZFp5_oLn;(go&y8c- z_4>!^#eXYgy+g4ZN{Yv3*1Bx#FDawTytuV!{ncjPP;p`g6ao)wGb+9|oQ^vg+R8)q zgxtxnw(0r1ct%N5$@+}EJxp2(<>8x})deOqOB_YfZ4Ff}0@goZyf?hj&o&m39igk8 zX)KvR*NGz^bh{lZllqc>_(tt%mx!|*y4^WT@ueH2iVc+#Z@3j=^188{L@{fb2;+>_ zZc{U-C`?z^}Ys>RON=qS%W&IlMO%=j!#+?;d?I11QzsNDbgV@6|l7yqd; zSIxjaxrxr=N}uky;bA~2;Rb!ySYWi1H)kuzh>>1Ly%x-%BWAl>(bQlWZl1t&&UtL1 zhg5?DGqYJ>=Gx@}B-`<|)Kqo9Jks7w^9-D1Sj+1Nh{-bb$2c#jMM1deyOY}X$bGyPtEQNP)R;In%H%2wJ@@@p?@vtwKEL??HqPP0f&^ia@Zm^@2mg{4l* z<)K1pnCg8J`&g-6IeJ>UhetEzPr~I7;;pHhfcrYn*lsWo$sXp&)3-;#TXo zG_v6u?#h2)O*vgQE3Qv!CBFM%X6XHlUv=&^!0e6fL644k&CtVE+5b}=6&A9G_Q|^H z&4WWAfDqAFV&XsVu4Mi{#)DUqaF8;rPL1Q^o#S1=vVX3!qXOu|ouPNc?OIlFD)({X(U*9RxbtQn15?#H$ zuxMy#0s;cni)!Z=qj_Z#uK1;I3nY4+r)7tMTrToH#Zqu^bqXByT|oH6SH1Ecf+LLX z-YIqM0W^?-!Dpk?9D4_PcSgRQ1Z^+c_#yW^dG|sBueA^rw<(HA@-$5$-pdg+)6aC2bdSo6A*vl4m+aIS>5&k#O6#sLR7`DcR%}R7u6+YAJ%{2lf57 zJ!Qz8A@GfX3Z^yzH#e-ky*-2)I;huuy|H0LxsLT=Wi(um-l!%?mW1O!(J^@8O&5J= zY^A$?fIepVS$@I*t7v)r(e8)#7o_Z9+q2unv>Wan)N0xW}yWk~oa?iyHB@)Uf66{>_O!hGSf|OQ6A3 z_{aE%_G68A3iA1Ha1h`5@G5x^B_4r`FH^h9&VBpoxEx9KcfwP zpN1cf8}=@@8lhH_Ij~S+PUZ;5)kW&CXn87=l3&WZ#na3Cs8Y|{-Rs25dvv66(hXQ% zTzuuCO`lO3v>{TqVOH}~<_VUfrljIKPYQhB*QBrK)YZcNgR#NExbz+?vCabhKAo$O zl5HP(0nhEHJCVopVSy|Tw0Ai!S|tn~34-SKJ}X5)jX63yHa6CkFH>@y%u;Trkd|hB zGqU5di;pYZ!h!NzPuKLHF8#GbtJ;Hie{>`l1+bOH(0Wfwl1gWd-^|&qpH>|u>ZJuf zG4^UuE{X3SqK-V4=AYCod^KXyer1G8nEWU^?02ujyi8y)aEg^`44$w(^m8P#&0~mA?k{ka1i*! zPd4|3BvXDR4I5L)W_@D~F>5iPQY9Ls;VGWkv){kt=(y@a zT^4*z%EmPRRX;5}Lbf5wJySc1Z1Y`g{L?VO-00zMuop(EbF|#7e*UKyC=?d|)-dli z5nNh=@iMP!1EKC3v|$%ID25%T)h`hz~;aVJOhA7olr5rU*Y77`%{xU(hNO10Ch0Q>}POg}TI?|7K$81mn*SP!T z%Ab8{(MHYJ&VBxZP5=vvjBx!MoQ3{jXX;b)Ty^)v_Q&O1gtwJKgc2~wq1 zxT1|e&033P6_1x7prHIlh0y!d(bh^u`LFb)~0^V7T%M!DNY@ne44c>Eo>AFX&wkbuDV!l9QACq zH;8WqLC4WEy~Kp$3(g6x^p^W2qVvr+_$m)|De{l6KA>BE-{q|aM40vdEbqK2Uhl9; z>YFQJ@(R_vqs~Cl{^I6!nd6v5-qS-={pd+{cY^J-L@Kod++o`pk6NcuHaFNEnpd() zB7Dd3mk-<|6f)9)$^nOTG2q7y=_fp_OLqP8#brMz#cQ8#aFkY9I6E(v5bO}ey!G>E zv`>SEB3?r0?x>I1&2z>tOLya_FbaX8e%{WSU+=w1IQ4(fp1TRY8Rb$VWexFnxiP~= zpqc(KzSbZ-H?*=xEs*Dvnr8c_P-YN!zN`M0xFs}y>}S}Oc2_f8B6W)rk7iQA*2N(>I%K9q}902#s_#sqNcwz3(p_3j%WUDqc*GpLm-3SEv z%^a(eWaB6#JR_U({PQ$`ao>pL2^EzPv`T#R2U((P>*1)v3uu4Ie1c{ruvvXv2Deqj zt#5Q}X4m?VZ1@IQF{<~Ik5_xvn}fw>FAOTV&mzuRzJ`~X2Y9YO#X(P+v6vS(XJnRy zH~R_dkgg8pql`WfBk?81ByyIAmo5)5W_z}JV3w2VgcaWIruhD_{BRS-jL4Oa(w_>G z_4tIanyXpf%9&t$ida9`~phLgvEZPQkQ+W#uooPHPEKRb3g#WM7% zbi82K%DQm)ondi@j-$buxuOBs>q4=Esq9B9x zwkoL-{d10$xo3kdakRy(6i^5(?)72$72y8fUcC_pyZHLI|>fLHv1Rujdg8F%2Nd~@!=t`LYX=>-Se;fxqt9&N z9g8apljcX8g!nxgUDDjbo5;8Qa5&@iG@ql3 zEiTVO#%Y;Nw_&*ZR{?qR{jtKiA1Ece@xZnFdHFRV#-S1PKET8eS1_asu@5l21Vnlp zoi*1|j48rKS|x0llbB!ujc&C&XsOo(+C7I}2SaT&Sz?EYq~e}zMbp)~^=zA6b?_;T}c4Kl#YLA!%yKVsK=7NzLz5o)z2I3`x=7GRWYV?#ebsb z_M|j|b1E}ncAn)KEv4$;igFPrIuu0P)#em;eW!Cfoi;CS^%Cy6>NZ$~%&!FU+&@a6 zJD)*alHqRf^N61%nvk+hUwz=J?mF;21AsRXwMkcdK!B@$V{;@m{zh3&7lwnQHRb%w zrH5nM<9+G0BT`XHEH;MW&x>}y`LEa!#6M4s=i-0m>fLC5?Al#+gZ{SERwu=wFs~eoIA~nL($`o_xil$OlR7(j{l8x>(0l-w(-_ zYT`rklB+9Wy?h?Jdvi%rU@%yukZ=)>#zdQ^51yp5yFY9sL&|E(RDt+%xv;%hN=Wq; z-brZ*ygj*J>G>}8l;*64#{DS z3U+GDU}kRIn+->QA@ad$%}?E=?yp83F%j|FnBahXjB?!NyNQ^cRaUTb!VOO6bf*N9 z;~w5?MrrT0DNAjS6KaR5vOum^XKcNxZhkc zu_EvPHd3qtfXF9}MhdkvG11LSE^ynWM7 z@e>RA`58T8QcoDyGiZwyiZsmYe6AAuqICXF0YUb9_}gvoP%Mg^Mi*6M+~i8)rhe8m zr48PT(fLO<-Ww;*kX`siE4jEfOTJeMy+eoHsmTU8mIl^BRlQZ0va89jd?ZZouG2}z z$e;9I7BziF9Sn7s$H8LCNF?k0s$T{jJvcmlA?49N_JFTV4r69tkax*(2y|qtahoCD zp`!YA8?EKRMCNqbLkH^i5n)M20_qUdB<&roxiQv;yvfpv>PyEtHQ?DxC8${YEXuol zOr1g~yO$)Wf^|1*3sX^d@n^_LC>!{KQ#s5rv9VnitHu`>Bd4t-lwgRu8D5)dKtc%u z`IU$gbfdvNixRU(>!{6sw{`4}qyu8bh>7T7w|7D0kB!0OD=YdojYQC)-`@P?fYm`*pyRhsnM&YOge{lTA|6oPI z=tiQ_=>J79v*%L1^$dC2I^Qtg^ntHO$k8GEckv`m;`{$jXt87h|69z5aK*YM-17an S@M#(f@{*QN5U&z74E#T{yp!_) literal 0 HcmV?d00001 diff --git a/doc/text/img/dependencies.svg b/doc/text/img/dependencies.svg new file mode 100644 index 0000000..eb71955 --- /dev/null +++ b/doc/text/img/dependencies.svg @@ -0,0 +1,4 @@ + + + +
common
runtime
libs
repl
compiler
\ No newline at end of file diff --git a/doc/text/img/stack-arr.png b/doc/text/img/stack-arr.png new file mode 100644 index 0000000000000000000000000000000000000000..1789385ec5ad47ea768bf4e8c30216a4f1d70673 GIT binary patch literal 37167 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 literal 0 HcmV?d00001 diff --git a/doc/text/requirements.md b/doc/text/requirements.md new file mode 100644 index 0000000..ebc3442 --- /dev/null +++ b/doc/text/requirements.md @@ -0,0 +1,14 @@ +--- +title: Requirements +--- + +1. Тема: Среда за изпълнение на „EcmaScript 5“ код за виртуалната машина на „Java“ +2. Изисквания + - Покритие на „ECMA-262 5.1 Edition“ стандарта + - Задоволително бързодействие + - Възможност за лесно вграждане в други софтуерни продукти + - Добри тестове, подробна документация и удобен и интуитивен публичен интерфейс за разработчици +3. Съдържание + 1. Теоретична част + 2. Практическа част + 3. Приложение \ No newline at end of file diff --git a/doc/text/src/build.lua b/doc/text/src/build.lua new file mode 100644 index 0000000..d614867 --- /dev/null +++ b/doc/text/src/build.lua @@ -0,0 +1,728 @@ +---@diagnostic disable: undefined-field +require "src.utils"; +local json = require "src.json"; + +local function reader(fd, ...) + if type(fd) == "string" then + fd = assert(io.open(fd, ... or "r")); + elseif type(fd) == "function" then + return fd; + else + assert(fd, ...); + end + + return function () + if fd == nil then error "File is closed" end + local res = fd:read(1024); + + if res == nil then + fd:close(); + fd = nil; + return nil; + else + return res; + end + end; +end + +local function read_to_str(fd, ...) + local res = {}; + + for val in reader(fd, ...) do + res[#res + 1] = val; + end + + return table.concat(res); +end + +local flag = {}; +function flag.has(name, ...) + if name == ... then + return true; + elseif ... == nil then + return false; + else + return flag.has(name, select(2, ...)); + end +end +function flag.add(name, ...) + if flag.has(name, ...) then + return ...; + else + return name, ...; + end +end + +local converters = {}; + +local function sanitize(str) + return (str + :gsub("<", "<") + :gsub(">", ">") + ); +end + +local function get_indent(ctx, n) + return ("\t"):rep((ctx.indent or 0) + (n or 0)); +end +local function indent(ctx) + ctx.indent = (ctx.indent or 0) + 1; +end +local function undent(ctx) + ctx.indent = (ctx.indent or 1) - 1; +end + +local function convert(data, ctx, ...) + local func = converters[data.t]; + if func == nil then + error(table.concat { "Unknown node '", data.t, "': ", to_readable(data) }); + else + return func(data.c, ctx, ...); + end +end +local function convert_all(arr, ctx, ...) + local res = array {}; + local plain = array {}; + local inline = true; + + for i = 1, #arr do + local a, b, c = convert(arr[i], ctx, ...); + res:append(a); + plain:append(b); + + if not c then + inline = false; + res:append("\n", get_indent(ctx)); + end + end + + return res, plain, inline; +end + +local function make_id(id, readable, ctx) + ctx.ids = ctx.ids or {}; + local res; + + if ctx.ids[id] ~= nil then + ctx.ids[id] = ctx.ids[id] + 1; + res = id .. "-" .. ctx.ids[id]; + else + ctx.ids[id] = 0; + res = id; + end + + if readable then + ctx.citables = ctx.citables or {}; + ctx.citables[id] = readable; + end + + return res; +end +local function last_id(id, ctx) + ctx.ids = ctx.ids or {}; + if ctx.ids[id] ~= nil and ctx.ids[id] > 0 then + return id .. "-" .. ctx.ids[id]; + else + return id; + end +end + +local function count_headers(array) + local res = 0; + + for i = 1, #array do + if not array[i].ignored then + res = res + 1; + end + end + + return res; +end + +local function read_attributes(data, readable, ctx, no_id) + local res = {}; + + if data[1] ~= "" then + res.id = data[1]; + end + for i = 1, #data[2] do + res[data[2][i]] = true; + end + for i = 1, #data[3] do + local curr = data[3][i]; + res[curr[1]] = curr[2]; + end + + if not no_id and res.id then + res.id = make_id(res.id, readable, ctx); + end + + return res; +end + +function converters.Table(data, ctx) + local options = data[3]; + local header = data[4]; + local body = data[5][1]; + + local text = array {}; + + local function convert_cell(cell, tag, options) + local align = options[1].t; + indent(ctx); + text:push("<", tag); + if align == "AlignRight" then + text:push [[ class="right"]]; + elseif align == "AlignLeft" then + text:push [[ class="left"]]; + elseif align == "AlignCenter" then + text:push [[ class="center"]]; + end + text:push(">\n", get_indent(ctx)); + text:append((convert_all(cell[5], ctx))); + undent(ctx); + text:push("\n", get_indent(ctx), ""); + end + + local function convert_row(row, tag) + text:push(""); + + for i = 1, #row[2] do + local cell = row[2][i]; + + indent(ctx); + text:push("\n", get_indent(ctx)); + convert_cell(cell, tag, options[i]); + undent(ctx); + end + + text:push("\n", get_indent(ctx), ""); + end + + text:push [[]]; + + indent(ctx); + indent(ctx); + + text:push("\n", get_indent(ctx, -1), ""); + text:push("\n", get_indent(ctx)); + convert_row(header[2][1], "th"); + text:push("\n", get_indent(ctx, -1), ""); + text:push("\n", get_indent(ctx, -1), ""); + + for i = 1, #body[4] do + text:push("\n", get_indent(ctx)); + convert_row(body[4][i], "td"); + end + + text:push("\n", get_indent(ctx, -1), ""); + + undent(ctx); + undent(ctx); + text:push("\n", get_indent(ctx), "
"); + + return text, array { "[table]" }; +end +function converters.BulletList(data, ctx) + local text = array { "
    " }; + local plain = array {}; + + indent(ctx); + for i = 1, #data do + local el_text, el_plain = convert_all(data[i], ctx); + + text + :push("\n", get_indent(ctx), "
  • ") + :append(el_text) + :push("
  • "); + plain:push("* "):append(el_plain):push("\n"); + end + undent(ctx); + + text:push("\n", get_indent(ctx), "
"); + + return text, plain; +end +function converters.OrderedList(data, ctx) + local text = array { "
    " }; + local plain = array {}; + + indent(ctx); + for i = 1, #data[2] do + local el_text, el_plain = convert_all(data[2][i], ctx); + + text + :push("\n", get_indent(ctx), "
  1. ") + :append(el_text) + :push("
  2. "); + plain:push(i .. ") "):append(el_plain):push("\n"); + end + undent(ctx); + + text:push("\n", get_indent(ctx), "
"); + + return text, plain; +end + +function converters.Code(data, ctx) + local content = data[2]; + + return array { "", sanitize(content), "" }, array { content }; +end +function converters.CodeBlock(data, ctx) + local attribs = read_attributes(data[1], nil, ctx); + local content = data[2]; + + local text = array { "
 0 then
+		text:push(" class=\"", classes:join " ", "\"");
+	end
+
+	text:push(">", sanitize(content), "
"); + + return text, array { content }; +end +function converters.Image(data, ctx) + local alt, alt_plain = convert_all(data[2], ctx); + local url = data[3][1]; + local title = data[3][2]; + local attribs = read_attributes(data[1], title or alt_plain:join " ", ctx); + + local classes = array {}; + if attribs.small then classes:push("small") end + if attribs.big then classes:push("big") end + + local text = array {} + :push("\"") 0 then + text:push(" class=\"", classes:join " ", "\""); + end + + text:push("/>"); + + return text, title and { title } or alt_plain or url or "[picture]"; +end +function converters.Figure(data, ctx) + local chapter_i = #(ctx.headers or {}); + ctx.figures_indices = ctx.figures_indices or {}; + ctx.figures_indices[chapter_i] = (ctx.figures_indices[chapter_i] or 0) + 1; + local figure_i = ctx.figures_indices[chapter_i]; + + local prefix = ("%s.%s."):format(chapter_i, figure_i); + + local attribs = read_attributes(data[1], prefix, ctx); + local id = attribs.id; + + indent(ctx); + indent(ctx); + + local name, name_plain = convert_all(data[2][1], ctx); + local content, plain = convert_all(data[3], ctx); + + undent(ctx); + undent(ctx); + local text = array { ""); + + text + :push("\n", get_indent(ctx, 1), "
", "\n", get_indent(ctx, 2)) + :append(content) + :push("\n", get_indent(ctx, 1), "
", "\n", get_indent(ctx, 1), "
", "\n", get_indent(ctx, 2)) + :push(prefix, " ") + :append(name) + :push("\n", get_indent(ctx, 1), "
") + :push("\n", get_indent(ctx), ""); + plain + :push(" (", prefix, " ") + :append(name_plain) + :push(")"); + + ctx.citables = ctx.citables or {}; + if id then ctx.citables[id] = prefix end + + return text, plain; +end +function converters.Div(data, ctx) + local attribs = read_attributes(data[1], nil, ctx, true); + + if attribs.figure then + local separator_i = data[2]:find_i(function (v) return v.t == "HorizontalRule" end); + + local content_data, title_data; + + if separator_i == nil then + content_data = array { data[2][1] }; + title_data = data[2]:slice(2); + else + content_data = data[2]:slice(1, separator_i - 1); + title_data = data[2]:slice(separator_i + 1); + end + + if #title_data == 1 and title_data[1].t == "Para" then + title_data = title_data[1].c + end + + return converters.Figure(array { + data[1], + array { title_data }, + content_data, + }, ctx); + else + error "Divs are not supported"; + end +end + +function converters.Cite(data, ctx) + local citation = data[1][1]; + local id = last_id(citation.citationId, ctx); + + local function target() + local res = (ctx.citables or {})[id]; + if res == nil then + io.stderr:write("WARNING! Citation '" .. id .. "' doesn't exist!\n"); + res = id; + end + + return res; + end + + return array { "
", target, "" }, array { target }, true; +end +function converters.Header(data, ctx) + local level = data[1]; + local attribs = read_attributes(data[2], nil, ctx); + + ctx.headers = ctx.headers or array {}; + ctx.header_stack = ctx.header_stack or array {}; + + local parent = ctx.header_stack[level - 1]; + if level > 1 and parent == nil then error "Heading hierarchy inconsistent" end + + local text, plain = convert_all(data[3], ctx); + + local header = { + id = attribs.id, + level = level, + children = array {}, + ignored = attribs.nonum or false, + }; + + local text_prefix, plain_prefix = "", ""; + + if level == 1 then + ctx.headers:push(header); + else + parent.children:push(header); + end + + if not header.ignored then + local prefix = array { count_headers(ctx.headers) }; + local text_format, plain_format; + + for i = 1, level - 1 do + --- @diagnostic disable-next-line: undefined-field + prefix:push(count_headers(ctx.header_stack[i].children)); + end + + header.prefix = prefix:join "."; + + if level == 1 then + text_format = ctx.chapter_format or "Chapter %s
"; + plain_format = ctx.chapter_format_plain or "Chapter %s: "; + else + text_format = "%s. "; + plain_format = "%s. "; + end + + text_prefix = text_format:format(header.prefix); + plain_prefix = plain_format:format(header.prefix); + end + + ctx.header_stack = ctx.header_stack:fill(nil, level + 1, #ctx.header_stack); + ctx.header_stack[level] = header; + + header.text = text_prefix .. text:join ""; + header.plain = plain_prefix .. plain:join ""; + + if attribs.id then + ctx.citables = ctx.citables or {}; + ctx.citables[attribs.id] = header.plain; + end + + return + arrays.concat({ (""):format(level, attribs.id), text_prefix }, text, { (""):format(level) }), + array { plain_prefix }:append(plain); +end +function converters.Link(data, ctx) + local content, content_plain = convert_all(data[2], ctx); + + local url = data[3][1]; + local attribs = read_attributes(data[1], nil, ctx); + local id = attribs.id or data[3][2]; + + if id == "" then id = nil end + + + if id then + ctx.link_i = (ctx.link_i or 0) + 1; + local i = ctx.link_i; + ctx.citables = ctx.citables or {}; + ctx.citables[id] = "[" .. i .. "]"; + end + + local plain = array {}; + plain:push(""); + plain:append(content); + plain:push(""); + + + return plain, content_plain:push(" (", url, ")"), true; +end + +function converters.Para(data, ctx) + indent(ctx); + local text, plain = convert_all(data, ctx); + undent(ctx); + return + array {} + :push "

" + :append(text) + :append "

", + plain; +end +function converters.Emph(data, ctx) + local text, plain = convert_all(data, ctx); + return arrays.concat({ "" }, text, { "" }), plain, true; +end +function converters.Strong(data, ctx) + local text, plain = convert_all(data, ctx); + return arrays.concat({ "" }, text, { "" }), plain, true; +end +function converters.Str(data) + return array { sanitize(data) }, array { data }, true; +end +function converters.Plain(data, ctx) + return convert_all(data, ctx); +end + +function converters.RawBlock(data) + return array {}, array {}, true; +end +function converters.MetaInlines(data, ctx) + return convert_all(data, ctx); +end + +function converters.LineBreak() + return array { "
" }, array { " " }, true; +end +function converters.SoftBreak() + return array { "­" }, array { " " }, true; +end +function converters.HorizontalRule() + return array { "
" }, array { " " }; +end +function converters.Space() + return array { " " }, array { " " }, true; +end + +local function parse_meta(meta) + local res = {}; + + for k, v in next, meta do + local text, plain = convert(v, {}); + res[k] = text:concat ""; + end + + return res; +end + +local function convert_headers(headers, ctx) + ctx = ctx or { indent = 0 }; + indent(ctx); + local res = arrays.concat( + { "
    " }, + headers:flat_map(function (child) + return arrays.concat( + { + "\n", get_indent(ctx), + "
  • ", + child.plain, + "Page" + }, + #child.children > 0 and convert_headers(child.children, ctx) or { "" }, + { "
  • " } + ); + end), + { + "\n", get_indent(ctx, -1), + "
" + } + ); + undent(ctx); + + return res; +end + +local function parse(...) + local stream, err = io.popen("pandoc --highlight-style kate --from markdown-raw_html --to json -o - " .. array { ... }:join " "); + local str = read_to_str(stream, err); + local raw = json.parse(str); + + return { + metadata = parse_meta(raw.meta), + content = raw.blocks or array {}, + }; +end + +local function convert_page(data, ctx) + ctx = ctx or {}; + ctx.headers = ctx.headers or array {}; + + local curr_page = array {}; + local pages = array {}; + + for i = 1, #data.content do + local curr = data.content[i]; + if curr.t == "Header" then + if curr.c[1] == 1 then + if #pages > 0 or #curr_page > 0 then + pages:append(curr_page); + end + curr_page = array {}; + end + end + + local text, _, inline = convert(curr, ctx); + curr_page:append(text); + if not inline then + curr_page:push("\n"); + end + end + + pages:append(curr_page); + + local function mapper(v) + if type(v) == "function" then + return v(); + else + return v; + end + end + + return { + content = pages:map(mapper, true):join "", + toc = convert_headers(ctx.headers):map(mapper, true):join "", + }; +end + +local function subst(template, variables) + local replaces = array {}; + + for k, v in next, variables do + local i = 1; + local search = "{{" .. k .. "}}"; + + while true do + local b, e = string.find(template, search, i, true); + if b == nil then break end + + replaces:push { b = b, e = e, v = v }; + i = e + 1; + end + end + + table.sort(replaces, function (a, b) return a.b < b.b end); + + local parts = array {}; + local prev = 1; + + for i = 1, #replaces do + local curr = replaces[i]; + parts:push(template:sub(prev, curr.b - 1), curr.v); + prev = curr.e + 1; + end + + parts:push(template:sub(prev)); + + return parts:join ""; +end + +function build(options) + local toc = options.toc; + local content = options.content; + local ctx = options.ctx or {}; + + print(table.concat { "Building ", array(options):join ", ", "..." }); + + local res = convert_page(parse(unpack(options)), ctx); + + if toc == nil and content == nil then + return res.content; + end + + local obj = {}; + if toc ~= nil then obj[toc] = res.toc end + if content ~= nil then obj[content] = res.content end + + return obj; +end + +function combine(objects) + local res = {}; + + for i = 1, #objects do + for k, v in next, objects[i] do + res[k] = v; + end + end + + return res; +end + +function template(options) + print("Templating into " .. options.template .. "..."); + options = combine(array { options }:append(options)); + return subst(read_to_str(io.open(options.template, "r")), options); +end + +function emit(values) + local f = io.stdout; + + if values.target then + f = assert(io.open(values.target, "w")); + print("Emitting to " .. values.target .. "..."); + else + print("Emitting to stdout..."); + end + + for i = 1, #values do + assert(f:write(values[i])); + end + + if f ~= io.stdout then + f:close(); + end +end + +-- return main; diff --git a/doc/text/src/json.lua b/doc/text/src/json.lua new file mode 100644 index 0000000..4a43186 --- /dev/null +++ b/doc/text/src/json.lua @@ -0,0 +1,244 @@ +local json = { null = {} }; + +-- Internal functions. + +local function kind_of(obj) + if type(obj) ~= "table" then return type(obj) end + + local i = 1; + for _ in pairs(obj) do + if obj[i] ~= nil then + i = i + 1; + else + return "table"; + end + end + + if i == 1 then + return "table"; + else + return "array"; + end +end + +local function escape_str(s) + local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" }; + local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" }; + for i, c in ipairs(in_char) do + s = s:gsub(c, "\\" .. out_char[i]); + end + return s +end + +-- Returns pos, did_find; there are two cases: +-- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. +-- 2. Delimiter not found: pos = pos after leading space; did_find = false. +-- This throws an error if err_if_missing is true and the delim is not found. +local function skip_delim(str, pos, delim, err_if_missing) + pos = string.find(str, "%S", pos) or pos; + + if string.sub(str, pos, pos) ~= delim then + if err_if_missing then + error(table.concat { "Expected ", delim, " near position ", pos }); + end + + return pos, false; + end + + return pos + 1, true; +end + +local esc_map = { b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", v = "\v" }; + +-- Expects the given pos to be the first character after the opening quote. +-- Returns val, pos; the returned pos is after the closing quote character. +local function parse_str_val(str, pos, check) + if pos > #str then error("End of input found while parsing string") end + + if check then + if string.sub(str, pos, pos) ~= "\"" then + return nil, pos; + else + pos = pos + 1; + end + else + pos = pos + 1; + end + + local res = {}; + + while true do + local c = string.sub(str, pos, pos); + if c == "\"" then + return table.concat(res), pos + 1; + elseif c == "\\" then + c = string.sub(str, pos + 1, pos + 1); + res[#res + 1] = esc_map[c] or c; + pos = pos + 2; + else + res[#res + 1] = c; + pos = pos + 1; + end + end +end + +-- Returns val, pos; the returned pos is after the number's final character. +local function parse_num_val(str, pos) + local num_str = string.match(str, "^-?%d+%.?%d*[eE]?[+-]?%d*", pos); + local val = tonumber(num_str); + + if not val then error(table.concat { "Error parsing number at position ", pos, "." }) end + return val, pos + #num_str; +end + +local json_end = { "eof" }; + +local function parse_impl(str, pos, end_delim) + pos = pos or 1; + if pos > #str then error("Reached unexpected end of input") end + + pos = str:find("%S", pos) or pos; + local c = str:sub(pos, pos); + local delim_found; + + if c == "{" then + pos = pos + 1; + + local key; + local obj = {}; + + c = string.sub(str, pos, pos); + if c == "}" then + return obj, pos + else + while true do + key, pos = parse_str_val(str, pos, true); + if key == nil then error("Expected a string key") end + + pos = skip_delim(str, pos, ":", true); -- true -> error if missing. + obj[key], pos = parse_impl(str, pos); + + pos, delim_found = skip_delim(str, pos, "}"); + if delim_found then return obj, pos end + + pos, delim_found = skip_delim(str, pos, ","); + if not delim_found then error("Expected semicolon or comma") end + end + end + elseif c == "[" then + pos = pos + 1 + + local arr = array {}; + local val; + local delim_found = true; + + while true do + val, pos = parse_impl(str, pos, "]"); + if val == json_end then return arr, pos end + if not delim_found then error("Comma missing between array items: " .. str:sub(pos, pos + 25)) end + + arr[#arr + 1] = val; + pos, delim_found = skip_delim(str, pos, ","); + end + elseif c == "\"" then -- Parse a string. + return parse_str_val(str, pos, false); + elseif c == "-" or c:find("%d") then -- Parse a number. + return parse_num_val(str, pos); + elseif c == end_delim then -- End of an object or array. + return json_end, pos + 1, true; + elseif str:sub(pos, pos + 3) == "null" then + return nil, pos + 4; + elseif str:sub(pos, pos + 3) == "true" then + return true, pos + 4; + elseif str:sub(pos, pos + 4) == "false" then + return true, pos + 5; + else + error(table.concat { "Invalid json syntax starting at position ", pos, ": ", str:sub(pos, pos + 10) }); + end +end + +local function stringify_impl(obj, all, indent_str, n) + local s = {}; -- We'll build the string as an array of strings to be concatenated. + local kind = kind_of(obj); -- This is 'array' if it's an array or type(obj) otherwise. + + if kind == "array" then + for i, val in ipairs(obj) do + s[i] = stringify_impl(val, all, indent_str, n + 1); + end + + if not indent_str then + return "[" .. table.concat(s, ",") .. "]"; + elseif #s == 0 then + return "[]"; + else + local indent = "\n" .. string.rep(indent_str, n + 1); + return table.concat { + "[", + indent, table.concat(s, "," .. indent), + "\n", string.rep(indent_str, n), "]" + }; + end + elseif kind == "table" then + for k, v in pairs(obj) do + local sep = indent_str and ": " or ":"; + local val = stringify_impl(v, all, indent_str, n + 1); + if val ~= nil then + if type(k) == "string" then + s[#s + 1] = stringify_impl(k) .. sep .. val; + elseif type(k) == "number" then + s[#s + 1] = "\"" .. k .. "\"" .. sep .. val; + elseif type(k) == "boolean" then + s[#s + 1] = "\"" .. k .. "\"" .. sep .. val; + end + end + end + + if not indent_str then + return "{" .. table.concat(s, ",") .. "}"; + elseif #s == 0 then + return "{}"; + else + local indent = "\n" .. string.rep(indent_str, n + 1); + return table.concat { + "{", + indent, table.concat(s, "," .. indent), + "\n", string.rep(indent_str, n), "}" + }; + end + return "{" .. table.concat(s, ",") .. "}"; + elseif kind == "string" then + return "\"" .. escape_str(obj) .. "\""; + elseif kind == "number" then + return tostring(obj); + elseif kind == 'boolean' then + return tostring(obj); + elseif kind == "nil" then + return "null"; + elseif all then + return tostring(obj); + else + return nil; + end +end + +-- local indent_str = " "; + +function json.stringify(obj, indent_str) + if indent_str == true then + indent_str = " "; + end + return stringify_impl(obj, false, indent_str, 0); +end +function json.pretty(obj) + return stringify_impl(obj, true, " ", 0); +end +json.null = {}; -- This is a one-off table to represent the null value. + +---@param str string +---@return unknown +function json.parse(str) + local obj = parse_impl(str, 1); + return obj; +end + +return json diff --git a/doc/text/src/perf.lua b/doc/text/src/perf.lua new file mode 100644 index 0000000..8134340 --- /dev/null +++ b/doc/text/src/perf.lua @@ -0,0 +1,30 @@ +local ffi = require "ffi"; + +ffi.cdef [[ + typedef struct timeval { + long tv_sec; + long tv_usec; + } timeval; + + int gettimeofday(struct timeval* t, void* tzp); +]]; + +local function now() + local target = ffi.new "timeval"; + ffi.C.gettimeofday(target, nil); + return tonumber(target.tv_sec) + tonumber(target.tv_usec) / 1000000; +end + +local function measure(func, ...) + local start = now(); + + return (function(...) + io.stderr:write(("Took %s seconds\n"):format(now() - start)); + return ...; + end)(func(...)); +end + +return { + now = now, + measure = measure, +} \ No newline at end of file diff --git a/doc/text/src/utils.lua b/doc/text/src/utils.lua new file mode 100644 index 0000000..a4a09f4 --- /dev/null +++ b/doc/text/src/utils.lua @@ -0,0 +1,429 @@ +-- TILL - TopchetoEU's "immaculate" lua libs +-- Some useful utilities every lua-er should use. +-- Might break shit, don't use in production for crying out loud + +-- Reimplement this for lua <5.1 +if table.move == nil then + --- @diagnostic disable: duplicate-set-field + function table.move(src, src_b, src_e, dst_b, dst) + if dst == nil then dst = src end + local offset = dst_b - src_b; + + if dst_b < src_b then + for i = src_e, src_b, -1 do + dst[i + offset] = src[i]; + end + else + for i = src_b, src_e do + dst[i + offset] = src[i]; + end + end + + return dst; + end +end + +-- Why did we *remove* this from the spec again? Classical PUC +unpack = table.unpack or unpack; +table.unpack = unpack; + +--- Prepares the object to be a class - puts an __index member in it pointing to the object itself +--- @generic T +--- @param obj T +--- @return T | { __index: T } +function class(obj) + --- @diagnostic disable-next-line: inject-field + obj.__index = obj; + return obj; +end + +-- arrays + +--- @alias array T[] | arraylib + +--- Converts the object to an array by putting "arrays" as its metatable +--- @generic T: unknown +--- @param obj T[] +--- @return array +--- @overload fun(val: string): array +function array(obj) + if type(obj) == "string" then + return obj:split ""; + else + return arrays.mk(obj); + end +end + +--- @class arraylib +arrays = class {}; + +--- Creates an array +function arrays.mk(obj) + return setmetatable(obj, arrays); +end + +--- Adds every element of every passed array to the end of this array +function arrays:append(...) + local res = self; + local n = #res + 1; + + for i = 1, select("#", ...) do + local curr = select(i, ...); + for j = 1, #curr do + res[n] = curr[j]; + n = n + 1; + end + end + + return res; +end + +--- Returns all the given arrays, concatenated to one +function arrays.concat(...) + --- @diagnostic disable-next-line: missing-fields + return arrays.append(array {}, ...); +end + +--- Adds all the given elements to the end of this array +function arrays:push(...) + return self:append({ ... }); +end +--- Removes the last element of the array and returns it +--- @generic T +--- @param self array +--- @return T val? The removed element, or nil if none +function arrays:pop() + local res = self[#self]; + self[#self] = nil; + return res; +end +--- @generic T +--- @param self array +--- @return T val? The last element of this array, or nil of none +function arrays:peek() + return self[#self]; +end + +--- Returns the result of mapping the values in table t through the function f +--- @param mutate boolean? If true, will operate directly on the given array +function arrays:map(f, mutate) + local out; + + if mutate then + out = self; + else + out = array {}; + end + + for i = 1, #self do + out[i] = f(self[i], i, self); + end + + return out; +end + +--- Finds the index of the given element, or nil if it doesn't exist +function arrays:find_i(f) + for i = 1, #self do + if f(self[i], i, self) then + return i; + end + end + + return nil; +end + +--- Like arrays:map, but will expect the function to return arrays, and will :append them to the result array instead +function arrays:flat_map(f) + local out = array {}; + + for i = 1, #self do + out:append(f(self[i], i, self)); + end + + return out; +end + +--- Sets each value from b to e to val in the given array +function arrays:fill(val, b, e) + if b == nil then b = 1 end + if e == nil then e = self end + if b < 0 then b = #self + 1 - b end + if e < 0 then e = #self + 1 - e end + + for i = b, e do + self[i] = val; + end + + return self; +end + +--- Every element from start to stop is removed from this array and are replaced with the given elements +function arrays:splice(start, stop, ...) + -- TODO: optimize + if select("#") > 0 then + local n = stop - start + 1; + + while n > 0 do + table.remove(self, start); + n = n - 1; + end + + for i = 1, select("#") do + table.insert(self, start, (select(i, ...))); + end + + return self; + else + local res = {}; + + for i = start, stop do + table.insert(res, self[i]); + end + + return res; + end +end + +--- Every element from start to stop is removed from this array and are replaced with the given elements +function arrays:slice(start, stop) + start = start or 1; + stop = stop or #self; + local res = array {}; + + for i = start, stop do + res:push(self[i]); + end + + return res; +end + +--- Equivalent of table.concat { table.unpack(self) } +--- @param sep string? Separator (defaults to empty) +--- @param b number? First element to take (defaults to beginning) +--- @param e number? Last element to take (defaults to end) +function arrays:join(sep, b, e) + return table.concat(self, sep, b, e); +end + +function arrays:__concat(other) + return arrays.concat(self, other); +end + +-- strings + +--- Splits the text into an array of separate lines. +--- @param self string +function string:split(sep) + sep = sep or ""; + local lines = arrays {}; + local pos = 1; + + if sep == "" then + for i = 1, #self do + lines:push(self:sub(1, 1)); + end + else + while true do + local b, e = self:find(sep, pos); + + if not b then + table.insert(lines, self:sub(pos)); + break; + else + table.insert(lines, self:sub(pos, b - 1)); + pos = e + 1; + end + end + end + + return lines; +end +--- Gets the nth character +--- @param self string +--- @param i number +function string:at(i) + return self:sub(i, i); +end +--- Performs a plain string replace +--- @param self string +--- @param old string +--- @param new string +function string:replace(old, new) + local b, e = self:find(old, 1, true); + + if b == nil then + return self; + else + return self:sub(1, b - 1) .. new .. self:sub(e + 1); + end +end + +local function escape_str(s) + local in_char = { "\\", "\"", "/", "\b", "\f", "\n", "\r", "\t" }; + local out_char = { "\\", "\"", "/", "b", "f", "n", "r", "t" }; + for i, c in ipairs(in_char) do + s = s:gsub(c, "\\" .. out_char[i]); + end + return s +end + +local function stringify_impl(obj, indent_str, n, passed) + local s = {}; -- We'll build the string as an array of strings to be concatenated. + local kind = type(obj); -- This is 'array' if it's an array or type(obj) otherwise. + + if kind == "table" then + if passed[obj] then return "" end + passed[obj] = true; + + local len = #obj; + + for i = 1, len do + s[i] = stringify_impl(obj[i], indent_str, n + 1, passed) .. ","; + end + + local keys = {}; + + for k, v in pairs(obj) do + if type(k) ~= "number" or k > len then + keys[#keys + 1] = { k, v }; + end + end + + table.sort(keys, function (a, b) + if type(a[1]) == "number" and type(b[1]) == "number" then + return a[1] < b[1]; + elseif type(a[1]) == "string" and type(b[1]) == "string" then + return a[1] < b[1]; + else + return type(a[1]) < type(b[1]) or tostring(a[1]) < tostring(b[1]); + end + end); + + for i = 1, #keys do + local k = keys[i][1]; + local v = keys[i][2]; + + local val = stringify_impl(v, indent_str, n + 1, passed); + if val ~= nil then + if type(k) == "string" then + s[#s + 1] = table.concat { k, ": ", val, "," }; + else + s[#s + 1] = table.concat { "[", stringify_impl(k, indent_str, n + 1, passed), "]: ", val, "," }; + end + end + end + + local meta = getmetatable(obj); + if meta ~= nil and meta ~= arrays then + s[#s + 1] = " = " .. stringify_impl(meta, indent_str, n + 1, passed) .. ","; + end + + passed[obj] = false; + + if #s == 0 then + if meta == arrays then + return "[]"; + else + return "{}"; + end + end + + local contents = table.concat(s, " "):sub(1, -2); + if #contents > 80 then + local indent = "\n" .. string.rep(indent_str, n + 1); + + contents = table.concat { + indent, + table.concat(s, indent), + "\n", string.rep(indent_str, n) + }; + else + contents = " " .. contents .. " "; + end + + if meta == arrays then + return table.concat { "[", contents, "]" }; + else + return table.concat { "{", contents, "}" }; + end + elseif kind == "string" then + return "\"" .. escape_str(obj) .. "\""; + elseif kind == "function" then + local data = debug.getinfo(obj, "S"); + return table.concat { tostring(obj), " @ ", data.short_src, ":", data.linedefined }; + elseif kind == "nil" then + return "null"; + else + return tostring(obj); + end +end + +--- Turns the given value to a human-readable string. +--- Should be used only for debugging and display purposes +function to_readable(obj, indent_str) + return stringify_impl(obj, indent_str or " ", 0, {}); +end + +function print(...) + for i = 1, select("#", ...) do + if i > 1 then + io.stderr:write("\t"); + end + + io.stderr:write(tostring((select(i, ...)))); + end + + io.stderr:write("\n"); +end + +--- Prints the given values in a human-readable manner to stderr +--- Should be used only for debugging +function pprint(...) + for i = 1, select("#", ...) do + if i > 1 then + io.stderr:write("\t"); + end + + io.stderr:write(to_readable((select(i, ...)))); + end + + io.stderr:write("\n"); +end + +-- functions + +--- @class functions +functions = class {}; + +--- Constructs a function, such that it calls the first function with the passed arguments, +--- the second function with the return of the first, and so on. The return value of the last function is returned +--- +--- In short, does the following, if the passed functions are a, b and c: return c(b(a(...))) +--- +--- Sometimes less cumbersome to write (a | b | c | d)(args...) than d(c(b(a(args...)))) +--- @param self function +function functions:pipe(...) + if ... == nil then + return self; + else + local next = ...; + + return functions.pipe(function (...) + return next(self(...)); + end, select(2, ...)); + end +end +--- Calls pipe with a and b; Alternative syntax for older Lua installation +function functions.__sub(a, b) + return functions.pipe(a, b); +end +--- Calls pipe with a and b +function functions.__bor(a, b) + return functions.pipe(a, b); +end + +-- It's not vital to have this metatable, so we will just try our very best +if debug then + debug.setmetatable(load, functions); +end diff --git a/doc/text/template.html b/doc/text/template.html new file mode 100644 index 0000000..1442f1d --- /dev/null +++ b/doc/text/template.html @@ -0,0 +1,555 @@ + + + + + + Дипломна работа {{author}} + + + + + + + + + + + + +
+
+ +

{{school_name}}

+
+ +
+

ДИПЛОМНА РАБОТА

+

по професия код {{profession}}

+

специалност код {{specialty}}

+
Тема: {{topic}}
+
+
+
+
Дипломант:
+
{{author}}
+
+
+
Научен ръководител:
+
{{supervisor}}
+
+
+
СОФИЯ - {{year}}
+
+ +
+
+ +

{{school_name}}

+
+
+
+
Дата на заданието: 28.10.{{prev_year}} г.
+
Дата на предаване: 28.01.{{year}} г.
+
+
+
Утвърждавам: ..............................
+
/{{ensurer_name}}/
+
+
+
+
+

ЗАДАНИЕ

+

за дипломна работа

+

ДЪРЖАВЕН ИЗПИТ ЗА ПРИДОБИВАНЕ НА ТРЕТА СТЕПЕН НА ПРОФЕСИОНАЛНА КВАЛИФИКАЦИЯ

+

по професия код {{profession}}

+

специалност код {{specialty}}

+
+
+ на ученика {{author}} от {{class}} клас
+ {{requirements}} +
+
+
+
Дипломант: ...........................................
+
/{{author}}/
+
+
+
Ръководител: ...........................................
+
/{{supervisor}}/
+
+
+
{{head_teacher_title}}: ...........................................
+
/{{head_teacher_name}}/
+
+
+
+
+ +{{content}} + + + + +