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