Уменьшил синтаксис Си ради 15 байт

от автора

Скучая на очередном уроке биологии я вспомнил о таком прекрасном творении как 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++; !!

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


ссылка на оригинал статьи https://habr.com/ru/articles/908558/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *