Атрибуты, макросы и статический аллокатор в Retrolang

Retrolang похож на C, но сходство лишь базовое. В действительности я не собираюсь копировать C — не только потому что это фактически мне не нужно, но и потому что программирование под PSX требует весьма специфических возможностей языка, почти на уровне DSL (при этом я не стал их прибивать гвоздями к MIPS, они теоретически могут быть реализованы на системном уровне для любой архитектуры).

Во-первых, это, конечно, атрибуты размещения и инициализации статических данных. Я почти сразу сделал поддержку вшивания в бинарник файлов — это очень полезно, чтобы писать мини-игры, которым не требуется читать файлы с диска, а также чтобы встроить в программу bitmap-шрифт или какие-то другие данные.

uchar data[] @("file.bin");

На PSX слишком мало памяти, чтобы использовать динамический аллокатор, как в современных ОС. Программа должна заранее распланировать память, которую она потребляет. Если доверить эту задачу компилятору, то он может все проверять и не допускать коллизий (в то время как ручная работа с диапазонами адресов чревата ошибками). Для этого в Retrolang используются все те же атрибуты:

macro Region = (0x80050000, 2048);
int someData[512] @Region;

Это статически выделит 512*4 байт в регионе MyRegion. Для этого в кодогенераторе предусмотрена специальная таблица регионов и аллокаций.

Преимущество такого подхода перед выделением статической памяти в секции данных в том, что бинарник остается маленьким, а это иногда критично. Кроме того, регионы поддерживают Scratchpad:

int someData[64] @SCRATCHPAD;

Выделенную память можно затем использовать в качестве арены для создания в рантайме более мелких объектов.

Во-вторых, AST-макросы. Это киллер-фича, которая делает ненужным препроцессор C. Препроцессор в Retrolang все равно есть, но возможности его ограничены, и я не планирую их развивать. Другое дело — макросы. Они позволяют переиспользовать практически любые выражения и утверждения языка, параметризуя их. Это похоже не шаблоны, но шаблон обычно параметризуется типами, а макрос — константами, идентификаторами, выражениями и другими макросами. Как и шаблоны, макросы — тоже разновидность метапрограммирования, но их главное назначение — явный инлайнинг кода. В условиях, когда вызов лишней процедуры — дорогое удовольствие, язык должен позволять переиспользовать код статически, во время компиляции. Например, вместо того, чтобы создавать функцию с командами GTE, можно сделать соответствующий макрос, и команды будут вставлены непосредственно в вызывающий код без оверхеда на движение по стеку:

macro gte_upload_transform(tr)
{
    gte_ctc2(GTE_TRX, tr->tx);
    gte_ctc2(GTE_TRY, tr->ty);
    gte_ctc2(GTE_TRZ, tr->tz);
    
    gte_set_matrix(tr->r);
    
    gte_ctc2(GTE_H,   tr->h);
    gte_ctc2(GTE_OFX, tr->ofx);
    gte_ctc2(GTE_OFY, tr->ofy);
    gte_ctc2(GTE_DQA, tr->dqa);
    gte_ctc2(GTE_DQB, tr->dqb);
}

Макросы также удобны для создания удобочитаемых оберток над функциями BIOS.

C3: неужели наконец-то нормальный C-подобный язык?

Современная замена C — это то, чего очень не хватает всем, кто пишет что-то для десктопа с нуля, не полагаясь на популярнные платформы типа веба, Java/.Net, скриптовых языков и т.д. До настоящего времени реального аналога C, можно сказать, не было вовсе (исключением является BetterC-режим D, но это все-таки не отдельный язык, а подмножество, и он не для «зашедших с улицы», а совсем наоборот — для самых опытных программистов на D). Go и Rust не предлагать! 🤣

И вот, захожу я как-то раз в ньюсгруппу d.D и вижу ссылку на C3: https://c3-lang.org. «Продать» мне что-то новомодное сложно, но язык зацепил! Я тестировал еще не все фичи, но вот что понравилось практически сразу:

  • Компилятор довольно легко установить. Вначале, правда, выкачивается майкрософтовский SDK, но после этого компилятор становится самостоятельным и Студию не требует;
  • Не нужна отдельная система сборки, компилятор сам выполняет ее функции;
  • Простая и вменяемая система типов, полностью совместимая с C без костылей;
  • Векторы, матрицы и кватернионы в стандартной библиотеке — бесценно! Сразу уйма работы отпадает при написании игрового движка;
  • Вообще, в целом, читаемая и красивая стандартная либа. Открываешь любой модуль, и все понятно;
  • Управление памятью, чем-то похожее на то, что я делаю в dlib (аллокаторы, универсальный new). Сборщика мусора нет;
  • Красивая обработка ошибок со встроенным Optional и проверкой на null на уровне синтаксиса. Optional — это просто модификатор типа: если его использовать, то компилятор тупо не дает прочитать такую переменную без предварительного null-чекинга. Можно его делать самостоятельно, а можно просто использовать оператор «!!», тогда компилятор вставит проверку сам. Все выглядит очень просто и читаемо.

Конечно, многого пока не хватает — например, нет менеджера пакетов. Биндинги к некоторым популярным библиотекам есть тут, но я пока не разобрался, как они линкуются, документации по этой теме нет. Нативных библиотек, ясное дело, тоже выбор пока совсем минимальный. Еще один жирный минус — не поддерживается компиляция в bare metal, а без этого язык нельзя назвать системным, и стопроцентной заменой C он считаться, строго говоря, не может.