Нам удалось достаточно разобраться в компиляторе blackboxcomponentbuilder (участвовали я, Иван Денисов и МихалНик, которого вы вряд ли знаете, а также все пользователи, отвечавшие на мои вопросы). Результат получился такой:
Код:
Печ(ITEM(TRUE)); (* Печатает Б $TRUE *)
Печ(ITEM(100500)); (* Печатает Ц 100500 *)
ITEM - это новая встроенная функция, которая берёт тип своего аргумента и на его основании подбирает функцию
NewDynamicallyTypedPtrFromЭТОТТИП, которую и вставляет вместо себя. Выражение возвращает указатель на DynamicallyTyped,
а DynamicallyTyped может сказать нам о своём типе и имеет функции AsBOOLEAN, AsINTEGER. AsINTEGER вернёт INTEGER, если тип - INTEGER, а в противном случае будет исключение.
Код - здесь
https://gitlab.com/budden/nkp В качестве побочного эффекта была произведена частичная «деобфускация», документирование и русификация кода компилятора, а также реализованы минимальные удобства для работы с исходниками в Visual Studio Code.
Другие типы пока не обрабатываются, но я уже понял, что решение через ITEM слишком уж аскетичное. И тут надо посоветоваться. На данный момент я хочу как-то так сделать:
Код:
PROCEDURE Печ(DYNAMICALLY_TYPED арг); (* формальный параметр имеет тип DynamicallyTyped, а фактический может иметь любой тип *)
BEGIN
CASE арг.тип OF
| типINTEGER: StdLog.Int(арг.AsInteger)
... END Печ;
Печ(100500);
Печ(TRUE);
И вторую вещь хочу я сделать:
Код:
PROCEDURE Список(Элемент1 ... ЭлементЭн);
PROCEDURE СписокЧерезСвязь(Тип: STRING; ПолеСледующий: STRING; DYNAMICALLY_TYPED Элемент1 ... ЭлементЭн);
Они достаточно разные. Первая из них принимает элементы любого (разного) типа и записывает их в некие CONS-ячейки.
Вторая - принимает элементы одинакового типа. Строит односвязный список со связью через поле с именем ПолеСледующий.
Первая функция динамичная (она даёт возможность реализации JSON и прочих динамических вещей), а во второй динамизм можно полностью исключить, если сделать функцию встроенной и во время компиляции разрешать типы.
Получаются такие вот «полумакросы».
Соответственно, прошу совета - кто что подобное делал, какие подходы можно применить. Тут наблюдается быстрое разрастание
добавляемых возможностей, а хочется обойтись минимумом.
Только давайте не будем обсуждать реализацию через наследование от базовой записи. Понятно, что можно сделать наследников от Meta.Value. Но тогда будет ненужный динамизм реализации (методы извлечения придётся делать виртуальными), и конструктору нужно будет явно указать тип, а это излишне, т.к. тип любого выражения компилятору и так известен. Тут речь идёт про механизм "типизированных макросов" и в общем виде способ действий такой:
берём узлы дерева компилятора с известными типами и берём процедуру. Производим некое вычисление от этих аргументов. И получаем (во время компиляции) некую новую процедуру, которую нужно вызвать вместо заданной. Либо вставляем преобразующий вызов при передаче параметра. Это совсем другой подход, чем виртуальные методы, и эти два подхода не взаимозаменяемы. Ни один из них не лучше, но вы применяете динамику там, где должна быть статика. В С++ это делается шаблонами, которые зачастую получаются ещё ужаснее, чем динамика.
И ещё - кто-то говорил, что есть статья про "умные макросы" для Оберона. Где можно её почитать?