Для использования кода на тех языках, которые транслятор использует в качестве выходных, необходим механизм привязки. Во многих встречавшихся мне трансляторах для этого используют расширения входного языка, позволяющих указать на принадлежность к другому языку, включая возможности вставки кода на этом языке. Это, отчасти, превращает транслятор в эдакий продвинутый препроцессор выходного языка.
В трансляторе "Востока" сейчас применён более простой модульный подход. Входной язык не содержит никаких расширений, а для связывания используется подмена кода на входном языке на код на выходном языке. То есть, интерфейсный модуль на Обероне с, возможно, пустыми типами и процедурами во время полной трансляции заменяется соответствующим файлом-"модулем" на С, Java или JavaScript, заполненным нужным функционалом.
Этот способ связки имеет важные преимущества:
- Отсутствует рваный стиль исходного модуля, сшитого из разнородных языков - каждый файл написан на одном языке. Нет необходимости представлять, как входной язык должен отображаться на выходные языки
- Выходные языки можно использовать по полной программе, лишь соблюдая возможность корректного связывания. Нет необходимости втискиваться в рамки предоставленного расширениями транслятора, но требуется соблюдать осторожность
- Заставляет программиста тщательней разделять связочные модули и основную логику, улучшая архитектуру. Расширенный же язык позволяет втискивать обращение к выходному языку куда угодно, размазывая внешние зависимости по программе
- Программа можеть быть собрана любым совместимым транслятором входного языка, ничего не знающим о несовместимых расширениях. Хотя, конечно, не любая такая программа может быть исполнена корректно без доработки
- Может потребоваться писать больше кода на выходном языке
- Несколько мест для изменений одной сущности
- Сложней вносить изменения, соблюдая целостность, в связку существующего модуля на входном языке и сопроводительные файлы на выходных, чем в единый модуль, написанном на расширении входного языка
Создание связки может выглядеть так:
- Создание интерфейсного модуля на Обероне, например, Print.mod:
MODULE Print; PROCEDURE Do*(str: ARRAY OF CHAR); BEGIN ASSERT(FALSE) END Do; END Print.
- Трансляция модуля, лежащего в текущем каталоге, в код на Си сюда же:
ost to-c Print . -m .
На выходе получаем Print.h:#if !defined HEADER_GUARD_Print # define HEADER_GUARD_Print 1 extern void Print_Do(o7_int_t str_len0, o7_char str[/*len0*/]); O7_INLINE void Print_init(void) { ; } #endif
и Print.c:#include <o7.h> #include "Print.h" extern void Print_Do(o7_int_t str_len0, o7_char str[/*len0*/]) { O7_ASSERT((0 > 1)); }
- Дополнение Си-файла нужными объявлениями и действиями:
#include <o7.h> #include "Print.h" #include <stdio.h> extern void Print_Do(o7_int_t str_len0, o7_char str[/*len0*/]) { printf("%s\n", str); }
- Запуск привязки. Указываем, что интерфейсный модуль (-i) и соответствующие файлы на Си (-c) лежат в
текущем каталоге:
ost run 'Print.Do("Проба микрофона")' -i . -c .
- Можно поместить файлы привязки в общий каталог print
mkdir -p print/singularity/definition print/singularity/implementation mv Print.mod print/singularity/definition/ && mv Print.[hc] print/singularity/implementation/ ost run 'Print.Do("Или так")' -infr print
Аналогичным образом пишутся привязки для Java:
mkdir -p print/singularity/implementation.java ost to-java Print print/singularity/implementation.java/ -m print/singularity/definition editor print/singularity/implementation.java/Print.java ost run-java 'Print.Do("Запуск через Java")' -infr printи для JavaScript:
mkdir -p print/singularity/implementation.js ost to-js Print print/singularity/implementation.js/ -m print/singularity/definition editor print/singularity/implementation.js/Print.js ost run-js 'Print.Do("Запуск через JavaScript")' -infr print
Комментариев нет:
Отправить комментарий