Objectionary: Dictionary and Factory for EO Objects

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

Со времен Кернигана и Ритчи мы делимся двоичным кодом в библиотеках. Вам нужно вывести некоторый текст с помощью printf() в C++? Вы получаете библиотеку libc с более чем 700 другими функциями внутри. Вам нужно скопировать поток в Java? Вы получаете Apache Commons IO с методом copy() и еще более чем 140 другими методами и классами. То же самое происходит во всех языках, о которых я знаю, таких как Ruby, Python, JavaScript, PHP: вам нужен объект, или класс, или функция, или метод - вам придется добавить всю библиотеку в свою сборку. Разве не было бы более элегантным иметь дело с отдельными объектами?

Идея не нова и не моя. Я позаимствовал ее из книги Object Thinking Дэвида Уэста, где он предложил создать Объектарий (страница 306), “сочетание словаря и фабрики объектов”, с следующими свойствами:

  • Каждый объект является автономной исполняемой сущностью;

  • Каждый объект имеет уникальный идентификатор и уникальный “адрес”.

  • Объекты - это просто совокупности объектов;

  • Объекты требуют аппаратно-специфических виртуальных машин для выполнения.

Семнадцать лет спустя (книга была опубликована в 2004 году), мы реализовали идею поверх EO, нашего нового языка программирования. Язык намеренно гораздо проще, чем Java или C++. Вы можете прочитать его более или менее формальное описание здесь.

Чтобы превратить программу на EO в исполняемый объект и разместить его в Objectionary, необходимо пройти через следующие обязательные этапы, предполагая использование JVM в качестве целевой платформы (этапы, отмеченные символом 🌵, реализованы нашим eo-maven-plugin):

  • Parse🌵: .eo.xmir

Разбор🌵: .eo.xmir

  • Оптимизировать🌵: .xmir ➜ лучше .xmir

  • Discover🌵: найти все иностранные псевдонимы

  • Pull🌵: download foreign .eo objects

Тянуть🌵: скачать иностранные объекты .eo

  • Разрешить🌵: скачать и распаковать артефакты .jar

  • Место🌵: переместить файлы артефакта .class в target/classes/.

  • Mark🌵: пометить исходные файлы .eo, найденные в .jar, как иностранные

  • ↑ Вернитесь в Parse, если некоторые файлы .eo все еще не распознаны

  • Транспиляция🌵: .xmir.java

  • Assemble🌵: то же самое, что и выше, но для тестов”

  • Компиляция: .java.class

  • Тест: запустить все модульные тесты

  • Unplace🌵: удаление файлов артефакта .class

  • Unspile🌵: удалить автоматически созданные файлы .java

  • Копирование: скопируйте файлы .eo внутрь .jar в папку EO-SOURCES/.

  • Развернуть: упакуйте артефакт .jar и поместите его в центр Maven.

  • Push: отправьте запрос на влитие (pull request) в yegor256/objectionary

  • Слияние: мы тестируем и объединяем pull request

Это итерационный процесс, который повторяется снова и снова, пока все необходимые объекты .eo парсятся, и их атомы присутствуют в виде файлов .class. Затем все файлы .xmir транспилируются в .java, а затем компилируются в двоичные файлы .class. Затем они тестируются, упаковываются и развертываются в Maven Central. Затем они объединяются с основной веткой master в Objectionary с помощью запроса на объединение.

Первая часть алгоритма может быть автоматизирована с помощью нашего плагина для Maven, просто поместив исходные файлы .eo в src/main/eo/ и добавив это в pom.xml:

Цель register будет сканировать каталог src/main/eo/, находить все исходные файлы с расширением .eo, и “регистрировать” их в специальном CSV-каталоге target/eo-foreigns.csv. Затем цель assemble будет вызывать следующие цели: parse, optimize, discover, pull и resolve. Все эти цели используют CSV-каталог при выполнении парсинга, оптимизации, извлечения и т. д.

Когда все они завершены, assemble проверяет каталог: нужно ли еще производить парсинг файлов с расширением .eo? Если да, то начинается еще один цикл с парсинга. Когда все файлы .eo распарсены, выполняется цель transpile, которая преобразует файлы .xmir в файлы .java и помещает их в target/generated-sources. Остальное делает стандартный плагин maven-compiler-plugin.

Давайте обсудим каждый шаг подробнее.

Скажем, это исходный код на .eo в файле src/main/eo/hello.eo:

Он будет разобран в XMIR (XML Intermediate Representation):

Если вы хотите узнать, что означает этот XML, прочитайте этот документ: там есть раздел о XMIR.

На этом шаге XMIR, созданный парсером, проходит через множество преобразований XSL, иногда получая дополнительные элементы и атрибуты. Наш пример XMIR может получить новый атрибут @ref, указывающий ссылку на объект user на строке, где объект был определен.

Некоторые XSL-преобразования могут проверять грамматические или семантические ошибки и добавлять новый элемент <errors/>, если что-то неправильно обнаружено. Таким образом, если разбор не обнаружил синтаксических ошибок, все остальные ошибки будут видны внутри документа XMIR, например, таким образом:

Кстати, это не настоящая ошибка, я просто выдумал ее.

На этом шаге мы определяем, какие объекты являются “иностранными”. В нашем примере объект user не является иностранным, поскольку он определен в коде, который у нас перед глазами, тогда как объект stdout не определен здесь и поэтому является иностранным.

Проходя через все файлы .xmir, мы легко можем определить, является ли объект иностранным, просто взглянув на их имена. Как только мы видим ссылку на org.eolang.io.stdout, мы проверяем наличие файла org/eolang/io/stdout.eo в каталоге со всеми исходными файлами .eo. Если файл отсутствует, мы добавляем имя объекта в каталог CSV и объявляем его иностранным.

Здесь мы просто пытаемся найти исходные коды файлов .eo для всех внешних объектов в Objectionary, просматривая его репозиторий GitHub. Например, здесь мы можем найти stdout.eo. Мы находим их там и загружаем на локальный диск.

Обратите внимание, мы загружаем исходники. Не бинарные или скомпилированные документы XMIR, а исходники в формате .eo.

Вот как может выглядеть stdout.eo после извлечения.

Объект представляет собой атом. Это означает, что хотя у нас есть его исходный код, он не является полным без куска платформо-специфического двоичного кода. Атом представляет собой объект, реализованный платформой выполнения, где выполняется программа EO (также известная как механизм FFI). Строка, начинающаяся с +rt (время выполнения), объясняет, где получить код времени выполнения. Часть jvm - это имя времени выполнения.

Мы переходим на Maven Central, находим там артефакт org.eolang:eo-runtime:0.10.2 и распаковываем его (в конце концов, это zip-архив с файлами .class).

Кстати, программа может содержать несколько мета-инструкций +rt, например:

Здесь три платформы времени выполнения будут знать, где получить отсутствующий код для атома stdout: EO➝Java обратится к Maven Central для JAR-артефакта, EO➝Ruby попытается найти гем с именем eo-core и версией 0.5.8 в RubyGems, в то время как EO➝Python попытается найти пакет eo-basics версии 0.0.3 в PyPi.

Затем мы помещаем все файлы .class, найденные в распакованном JAR, в каталог target/classes. Мы делаем это, чтобы помочь плагину компилятора Maven найти их в пути классов.

В каждом поступающем JAR-файле мы можем найти исходные файлы .eo. Это программы, которые присутствовали в classpath при сборке данного JAR-файла. Мы также рассматриваем их как внешние объекты и добавляем их в CSV-каталог.

Когда все иностранные объекты, зарегистрированные в каталоге, скачаны, скомпилированы и оптимизированы, мы готовы начать транспиляцию. Вместо прямой компиляции XMIR в байт-код, мы транспилируем его в .java и позволяем компилятору Java выполнить задачу по генерации байт-кода.

Мы считаем, что есть несколько преимуществ транспиляции в Java по сравнению с компиляцией в байт-код:

  • “Используется мощность оптимизации существующих компиляторов.”

  • Сложность транспайлера ниже, чем у компилятора.

  • Переносимость кода вывода выше.

У нас уже есть два транслятора с EO в Java: канонический и тот, который создал Университет НИУ ВШЭ. У нас также есть экспериментальный транслятор EO в Python, созданный студентами Университета Иннополис. Вероятно, к моменту вашего прочтения этой статьи появятся еще больше трансляторов.

Несмотря на то, что мы верим в трансляцию, всегда есть возможность создать компиляторы EO➝Bytecode, EO➝LLVM или EO➝x86. Вы можете смело попробовать!

На этом этапе стандартный Maven Compiler Plugin находит автоматически сгенерированные файлы .java в target/generated-sources и преобразует их в файлы .class.

Здесь мы удаляем все файлы .class, распакованные из зависимостей. Это необходимо, чтобы избежать их упаковки в итоговый JAR-файл.

Мы делаем размещение и затем снятие просто потому, что плагин Maven Compiler не позволяет нам расширять classpath во время выполнения. Если бы это было возможно, мы бы просто загружали зависимости из Maven Central и добавляли их в classpath без распаковки, размещения и последующего снятия.

Здесь мы удаляем все файлы .class из каталога target/classes/, которые были автоматически созданы из файлов .eo. Мы не хотим отправлять двоичные файлы, которые могут быть созданы из исходных файлов .eo. Мы хотим отправлять только атомы, которые являются исходными файлами .java.

На этом шаге мы берем все исходные .eo файлы из src/main/eo/ и копируем их в каталог target/classes/EO-SOURCES/. Позже они будут упакованы вместе с файлами .class в .jar, который будет размещен в Maven Central. При копировании мы заменяем 0.0.0 в версии времени выполнения на текущую развертываемую версию. Взгляните на файл stdout.eo в его репозитории исходного кода.

Версия на линии +rt - 0.0.0. Когда исходные файлы копируются в JAR, этот текст заменяется.

Мотивация для отправки исходных файлов вместе с бинарными файлами следующая. Когда бинарные файлы атомов компилируются из Java в байт-код, они остаются рядом с транслированными исходными файлами. Они компилируются вместе. Более того, модульные тесты также зависят как от исходных файлов атомов, так и от автоматически сгенерированных/транслированных исходных файлов. Мы хотим, чтобы будущие пользователи JAR знали, какие исходные файлы у нас были на месте во время компиляции, чтобы, возможно, позволить им воспроизвести ее или хотя бы знать, каковы были условия окружающей среды для полученных ими бинарных файлов.

С практической точки зрения, нам нужны эти исходные файлы в JAR, чтобы позволить шагу Mark понять, какие объекты стоит привлечь рядом с разрешенными атомами.

Здесь мы упаковываем все из target/classes/ в JAR-архив и размещаем его в Maven Central.

Я предлагаю также размещать исходные коды на GitHub Pages, чтобы пользователи могли просматривать их в Интернете. Кроме того, это будет полезно позже, когда мы сделаем pull request в Objectionary. Проверьте этот скрипт .rultor.yml в одной из моих библиотек EO, он размещает исходные коды .eo на GitHub Pages, заменяя в них версионные маркеры 0.0.0 правильно.

Когда развертывание завершено и серверы CDN Maven Central обновляются, приходит время отправить pull request в yegor256/objectionary. Исходные файлы .eo объектов помещаются в objects/, а модульные тесты - в tests/. В основном, мы просто копируем src/main/eo/ и src/test/eo туда. Но, стоп… одна важная деталь. В исходных файлах, как уже было сказано ранее, у нас установлены версии +rt равные 0.0.0. Здесь, когда мы копируем в Objectionary, версии должны быть установлены как реальные числа.

Когда поступает запрос на объединение, предварительно настроенное действие GitHub в репозитории yegor256/objectionary транспилирует все исходные файлы .eo на все известные платформы и выполняет все модульные тесты. Если все чисто, мы рассматриваем запрос на объединение и решаем, соответствуют ли предлагаемые объекты другим уже присутствующим в Objectionary.

После объединения запроса на объединение объекты становятся частью централизованного словаря всех объектов EO. Взгляните на этот запрос на объединение, в котором был представлен новый объект в Objectionary после размещения его атома в Maven Central.

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-16 at 15:29

sixnines availability badge   GitHub stars