Скучая на очередном уроке биологии я вспомнил о таком прекрасном творении как brainfuck и о его интерпретаторах. А вернее я вспомнил что самый маленький интерпритатор был написан ещё в бородатых годах и занимал что-то около 150 байт.

Вернувшись домой я нашел самый маленикий среди них:

s[99],*r=s,*d,c;main(a,b){char*v=1[d=b];for(;c=*v++%93;)for(b=c&2,b=c%7?a&&(c&17?c&1?(*r+=b-1):(r+=b-1):syscall(4-!b,b,r,1),0):v;b&&c|a**r;v=d)main(!c,&a);d=v;}

Он написан на Си, занимает 160 байт иии... Это много.

Посмотрите как много место занимает обьявление функции main. Одним словом: ужас.

Решение очевидно - нужно уменьшить синтаксис насколько это возможно.

Есть два стула...

Первый стул - создать свой язык с минимальным синтаксисом и кучей сахара на основе существующего компилятора Си.

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

Выбор очевиден. Писать свой компилятор даже на основе готового - смерть, долгая и мучительная. Наша программа должна просто заменить в исходном файле некоторые части, записать итог в файлик и отдать этот файлик gcc. Так что, сделав умный вид, я принялся обдумывать синтаксис языка microC.

Синтаксис языка и что вообще нужно заменять

Синтаксис я решил построить на макросах - специальных конструкций, развёртывающихся в Си код согласно параметрам.

Взяв первый попавшийся, неиспользуемый символ в Си - тильда (~), я принялся плясать вокруг этого. И решил сделать для макросов подобный синтаксис:

~<назвение макроса (один символ)><тело макроса>!

Или для более сложных конструкций:

~<назвение макроса><осложняющая часть (дальше просто ОЧ)>:<тело>!
Что такое осложняющая часть

На примере if и while хочу показать что такое осложняющая часть

if (a == 5) {
  printf_s("a == 5");
}

while (a > 5) {
  printf_s("a--");
  a--;
}

a == 5 и a > 5 и есть осложняющая часть в if и while соответственно

Некоторые макросы

~M<тело>!

main функция

main (a,b) {тело}

~w<ОЧ>:<тело>

while цикл

while (ОЧ) {тело}

~i<ОЧ>:<тело>

if

if (ОЧ) {тело}

Для использования символа '!' как отрицание следует использовать '`'


Так же переменные и функции претерпели некоторые изменения

// Си код
char Cvar = 5;

/microC код/
7MCvar5;

Для начала я решил опустить смвол равенства, тем самым сделав невозможным использование цифр в названии переменной. Можно догадаться, что тип char превратился в 7, но почему именно 7 и какими цифрами обозначаются другие типы.

Типы данных

Самый компактный способ записи - числа - был найден сразу, но какие числа должы соответствовать каким типам?

Вспомним, что сколько в С выделяется байт под каждый тип в памяти:

char

8

short

16

int

32 (ну не всегда, но примем все-таки за 32)

long

64 (ситуация как с int)

Решено! будем обозначать типы их размером. Только вот под само число в знаковом типе выделяется на один бит меньше, чем в верхнеописаной таблице, поэтому будем обозначать типы по количеству бит, отведённых в них под само число, игнорируя бит знака. И того вот итоговая таблица типов:

0

пустой тип для указания своих, которые microC не потдерживает

1

void

7

char

8

unsigned char

15

short

16

unsigned short

31

int

32

unsigned int

63

long

64

unsigned long

Небольшая проблемка и как её исправить

Для присвоения одной переменной, значения другой в C используется подобный синтаксис:

a = b;

Но в microC появиться знак | на месте пробела, иначе ab будет расценена как имя переменнойкоторую мы создаем

a|b;

И, к сожалению, итоговое количество знаков не изменилось...

Большая проблемка и как её я не исправил

Если упрощать, то компилятор microC работает так:
1. находит тип из таблицы
2. читает все последующие буквы и интерпритирует их как имя переменной
3. все что идет дальше (или после знака '|') просто записывает в итоговый Си файл

Такой же метод работает и для осложняющей части.

и поэтому какой-нибудь хитрый код, по типу

7var|a==5 ? b|k : G|k;

// в Си это выглядело бы так:
char var = a == 5 ? b=k : G=k;

// но из-за вышеназвонной проблемы в microC это будет выглядеть все же так:
7var|a==5 ? b=k : G=k;

В целом, есть ещё пару изменений в синтаксисе, но вы сможете самостоятельно почитать про них в github репозитории.

Итог. Переписывание интерпритатора на microC

После долгого и мучительного переписывания интерпритатора получилось следующее

7s[99],*d,c;*r|s;~M7*v1[d=v];~wc=*v++%93:b|c&2,b=c%7?a&&(c&17?c&1?(*r+=b-1):(r+=b-1):syscall(4-!b,b,r,1),0):v;~wb&&c|a**r:main(!c,&a);v|d;!!d=v;!
Форматированая версия
7s[99],*d,c;
*r|s;
~M
  7*v1[d=v];
  ~wc=*v++%93:
    b|c&2,b=c%7?
      a&&
        (c&17 ?
          c&1 ?
            (*r+=b-1):
            (r+=b-1):
              syscall(4-!b,b,r,1),0):v;
    ~wb&&c|a**r:
      main(!c,&a);
      v|d;
    !
  !
  d=v;
!

Итого: 145 байт! И это на 15 байт меньше чем оригинал!

Также я написал ещё и свою версию на 250 байт

~Istdio.h!
7a[30000];7*p|a;
7*l[99];7i0;
~m7*s1[v];7*k|s;
~w*k!='!':
~S*k:
~c60:p--;~b
~c62:p++;~b
~c43:*p+=1;~b
~c45:*p-=1;~b
~c44:*p|getchar();~b
~c46:putchar(*p);~b
~c91:~i*p!=0:l[i++]|k;~b!~w*k!=93:k++;!~b
~c93:~i*p!=0:k|l[i-1];~b!i--;~b!
k++;
!!

Спасибо за прочтение. Код компилятора и гайд по языку лежат на гитхабе:

Комментарии (5)


  1. rsashka
    12.05.2025 09:42

    a == 5 и a > 5 и есть осложняющая часть в if и while соответственно

    Не будет ли проще и понятнее назвать это "условием"?


  1. datacompboy
    12.05.2025 09:42

    Для использования символа '!' как отрицание следует использовать '`'

    а как использовать тильду как тильду? (~ -- это побитовое отрицание)


  1. CBET_TbMbI
    12.05.2025 09:42

    И это на 15 байт меньше чем оригинал!

    А как это отразилось на скорости компиляции и скомпилированного? Никак? Или какие-нибудь отличия могут быть? Даёшь тест!


  1. wataru
    12.05.2025 09:42

    Вообще странный подход. Если вы пишите программу, которая генерирует код, то почему бы не ввести специальную команду "@" - которая означает "интерпретатор брейнфака". Вот вы и ужали всю программу до 1 символа!


  1. Reposlav
    12.05.2025 09:42

    Это забавный эксперимент, однако думаю, что лучше смотреть на размеры бинарника, а не исходного кода