Переотправлю письмо через Яндекс, а то с Mail.Ru какие-то проблемы.

Добрый день всем!

Как многие в рассылке знают, я работаю над компилятором Рефала-5λ — точного 
надмножества Рефала-5. Одним из полезных расширений языка являются нативные 
вставки — вставки кода C++ в исходном коде на Рефале. Например,

/* это файл на Рефале */
%%
#include <stdio.h>
%%

$ENTRY Go {
  = <Hello> <Hello>
}

Hello {
%%
  printf("Hello!\n");
  refalrts::splice_to_freelist(arg_begin, arg_end);
  return refalrts::cSuccess;
%%
}

Как это работает. Компилятор Рефал-5λ создаёт исполнимые файлы операционной 
системы (.exe-файлы для Windows, ELF для Linux, какие-то ещё для macOS). 
Скомпилированный файл устроен на манер SFX-архива: он состоит из 
префикса-интерпретатора, исполнимого файла операционной системы, и 
суффикса-интерпретируемого кода — двоичного языка сборки. Как если бы в конец 
интерпретатора refgo приписали бы несколько .rsl-файлов.

Когда компилятор транслирует файлы без нативных вставок, то он берёт уже 
готовый префикс-интерпретатор (скомпилированный ранее) и в конец к нему 
приписывает файлы языка сборки.

Если среди исходных файлов есть файлы с нативными вставками, то компилятор из 
таких файлов создаёт исходники на C++, содержащие текст этих самых нативных 
вставок. Потом вызывает компилятор C++, передавая ему исходники интерпретатора 
языка сборки и эти самые файлы с текстом вставок. Получается исполнимый файл, 
который становится префиксом-интерпретатором. Функции, описанные на C++ (вроде 
функции Hello в примере выше) становятся своего рода встроенными в 
интерпретатор функциями.

Кстати, все библиотечные функции (Prout, Add, Sub, Type) тоже описаны в файле 
на Рефале примерно таким образом: 
https://github.com/bmstu-iu9/simple-refal/blob/master/src/srlib/Library.sref

 

Но уже давно я планирую реализовать динамическую загрузку модулей на манер 
.dll/.so. В этом случае компилятор сможет порождать не-.exe модули, которые 
могут подгружаться как статически (если прописана соответствующая ссылка в 
языке сборки), так и динамически (вызывается соответствующая библиотечная 
функция для загрузки модуля и поиска функции в нём). Если исходные файлы модуля 
не содержат нативных вставок, то создаётся динамически загружаемый модуль 
только с одним языком сборки. Если содержат — вызывается компилятор C++, 
который создаёт .dll/.so операционной системы с нативными функциями, и к нему в 
хвост приписывает язык сборки как раньше.

 

На этом пути много технических проблем, я с ними справлюсь, скорее всего. Но 
вот возникла одна архитектурная, про которую я не вижу красивого гладкого 
решения.

Нужно (как и в случае .dll/.so) обеспечить корректную обработку повторной 
загрузки модуля. Т.е. если некоторый файл был загружен, не грузить его 
повторно, а только увеличивать у него счётчик ссылок.

Но вот как понять, что он уже был загружен? По пути к файлу это сделать сложно. 
На UNIX-системах один и тот же файл может быть доступен как subdir/filename, 
./subdir/./././filename /home/user/subdir/filename, 
/home//user///subdir////filename, 
/home/otheruser/../user/otherdir/../subdir/filename и т.д. На Windows ещё 
веселее: добавляются тонкости с регистром символов в именах, синонимичность / и 
\ и текущие папки на разных дисках. Эмулировать всё это слишком громоздко и тем 
самым некрасиво.

Можно к каждому файлу добавлять хэш, таким образом обнаруживать идентичные 
модули по их содержимому (даже если у них разные имена файлов). Но мне 
почему-то это кажется костылём.

 

С уважением,
Александр Коновалов

Ответить