Программисты на Руси программируют на Си. RUSI: Концепт билингвального компилятора

от автора

Это статья про разновидность языка Си с русским и оригинальным английским синтаксисом, работающими одновременно. «Если» и «if» работают как синонимы. Это не интерпретатор. Это компилятор, который способен собрать себя сам. Это даже не транспилятор русского в английский — оба набора слов работают равноценно. Код можно посмотреть https://кодыч.рф/administrator/rusi.

Как это выглядит

// Английский main с русским типом и операторомint main() {    int x = 42;    if (x == 42) {        возврат 0;    }    return 1;}// Полностью русский синтаксисцел главн() {    цел ъ = 42;    если (ъ == 42) {        возврат  0;    }    возврат 1;}// Смешанный стильцел main() {    printf("Легкоступов! Ты чего написал?");    возврат 0;}

Философия проекта

Латиница везде и во всем. На английском пишут код для ЭВМ, математики вечно ищут икс, аккорды для гитары на импортном пишутся (E G A E G B A E G A G E), номера автомобилей в РФ используют только те буквы, которые совпадают с латиницей (не ломать же им там свой мозг пониманием букв «Ы» или «Щ»).

RMS владея английским и испанским — явно не думал же он о том, что в Евразии еще много кто живет, когда создавал свой опенсорс. Создавал он для своей идеи. О чем это говорит? Мы не создаем — мы импортируем. А если мы не создаем — мы не понимаем. Инженерное мышление должно подсознательно ощущать недостатки и опасности таких импортов. Это даже не вопрос пресловутого «импортозамещения», на которое у всех негатив, а вопрос подходов к формированию и осознанию окружающих нас технологий, как ни странно.

Работники ИТ в России двуязычны, как и везде где английский не основной язык. Создать двуязычный компилятор казалось бы — это вполне естественно. Хотя тот же Рю Уэяма не сделал японский компилятор. Авторы Луа из Бразилии не сделали бразильский. Авторы Котлина из Петербурга не стали делать его кириллическим. Что их всех остановило? Они все однозначно знали что-то кроме инглиша.

Как оно вообще зародилось?

Вкратце: пишу с нуля свой язык программирования. Ассемблер на русском. https://кодыч.рф/administrator/kvs. Пишу его на Python. И вот мне захотелось написать компилятор с русскими кириллическими мнемониками на кириллическом же языке. Где бы такой взять? В РФ есть такой продукт. Своеобразный и для специфических задач, платный и проприетарный. Хочу опенсорс. Решение — сделать самому. Вот уж парадокс курицы и яйца.

Ранее была статья https://habr.com/ru/news/963440/, где было рассказано о внедрении русифицированного JS в образовании. Переделать интерпретируемый язык, изначально заточенный под веб — легко. Там сразу работает юникод почти на всех уровнях. Но системный язык хочется видеть сделанным на системном языке. Значит, переделывать нужно системный язык программирования.

На просторах GitHub был обнаружен компилятор chibicc гения Рю Уэямы (Rui Ueyama), и главное — минималистичный, легче переделывать.

Трудности перевода

Чтобы перевести, нужно вкратце описать как работает компилятор. Он потребляет слова и переводит их в узлы AST — абстрактного синтаксического дерева. Казалось бы, идея проста. Для логики читающей узлы AST уже не важно, как называлось слово на человекочитаемом языке, и можно просто поменять список ключевых слов. Но не все так просто. В компиляторостроении традиционным считается подход потреблять слова и операторы побайтово, а не посимвольно.

Для нас это означает, что просто подставить слово на великом и могучем не выйдет — каждая кириллическая буква занимает по 2 байта. Такой компилятор соберется, но работать не будет.

Исключением могут считаться, разве что интерпретируемые языки, заточенные под веб — там юникод изначально заложен как основной способ кодировки. К таким я отнесу JS и PHP. Хуже дела обстоят с интерпретаторами, спроектированными для прикладных задач. Lua и Python борются за производительность и любят использовать потребление именно по байтам. FASM архитектурно вообще имеет систему фильтрации на основе таблицы с допустимыми символами.

Техническая реализация

Главная функция в модуле unicode.c, которая отвечает за посимвольный парсинг вместо побайтового, это decode_utf8().

uint32_t decode_utf8(символ **нов_поз, символ *п) {    беззнак символ первый = (беззнак символ)*п;    если (первый < 0x80) {        *нов_поз = п + 1;        возврат первый;    }    цел длина;    uint32_t рез;    uint32_t мин_знач;    если ((первый & 0xF8) == 0xF0) {        длина = 4;        рез = первый & 0x07;        мин_знач = 0x10000;    } иначе если ((первый & 0xF0) == 0xE0) {        длина = 3;        рез = первый & 0x0F;        мин_знач = 0x800;    } иначе если ((первый & 0xE0) == 0xC0) {        длина = 2;        рез = первый & 0x1F;        мин_знач = 0x80;    } иначе {        error_at(п, "некорректная UTF-8 последовательность");    }    для (цел ш = 1; ш < длина; ш++) {        беззнак символ байт = (беззнак символ)п[ш];        если ((байт & 0xC0) != 0x80)            error_at(п, "некорректная UTF-8 последовательность");        рез = (рез << 6) | (байт & 0x3F);    }    если (рез < мин_знач)        error_at(п, "избыточная UTF-8 последовательность");    если (рез >= 0xD800 && рез <= 0xDFFF)        error_at(п, "суррогатные пары недопустимы в UTF-8");    если (рез > 0x10FFFF)        error_at(п, "кодпоинт превышает U+10FFFF");    *нов_поз = п + длина;    возврат рез;}

В оригинальном chibicc (и во многих классических компиляторах) лексер при чтении исходного кода работает с байтами. Он видит поток байтов и интерпретирует их как символы ASCII. Для кириллицы это было бы катастрофой, потому что русская буква «А» в UTF-8 занимает два байта (0xD0 0x90), и лексер увидел бы два разных «токена» вместо одной буквы.

Функция decode_utf8() решает эту проблему, выполняя преобразование байтового потока в поток кодпоинтов (символов Unicode). Она работает следующим образом:

  1. Анализ первого байта: Функция смотрит на первый байт последовательности и определяет, сколько байтов занимает текущий символ (1, 2, 3 или 4 байта).

  2. Валидация: Она проверяет, что последующие байты являются корректными “продолжениями” (их старшие биты равны 10).

  3. Сборка: Функция извлекает значащие биты из каждого байта и собирает их в одно целое 32-битное значение — код символа Unicode.

Теперь, вместо того чтобы просто взять байт (*p++), лексер в tokenize.c использует новую функцию для получения следующего символа:

// Вместо побайтового чтения:// c = *p++;// Используется посимвольное чтение:c = decode_utf8(&p, p);

Это фундаментальное изменение. Оно означает, что весь последующий код (проверка на is_ident1, is_ident2 и т.д.) работает уже не с байтами, а с полноценными символами Unicode. Именно благодаря этому можно использовать в коде переменные счётчик и ключевое слово если. decode_utf8() — это «переходник» между миром байтов (на уровне файла) и миром символов (на уровне логики компилятора).

Далее уже можно сделать так:

стат бул зарезервированное_слово(Token *ток) {    стат ХешТаблица карта;    если (карта.вместимость == 0) {        стат символ *ключ_слова[] = {            "return", "if", "else", "for", "while", "int", "sizeof", "char",            "возврат", "если", "иначе", "для", "пока", "цел", "размер", "символ",            "struct", "union", "short", "long", "void", "typedef", "_Bool",            "структ", "объед", "корот", "длин", "пусто", "типдеф",            "enum", "static", "goto", "break", "continue", "switch", "case",            "переч", "стат", "перейти", "прерви", "продолжи", "выбор", "случай",            "default", "extern", "_Alignof", "_Выравнивание_типа", "_Alignas", "_Выравнивание", "do", "signed",            "_Generic", "_Обобщ", "умолч", "внеш", "делай", "знак",            "unsigned", "const", "volatile", "auto", "register", "restrict",            "беззнак", "конст", "летуч", "авто", "регистр", "огранич",            "__restrict", "__restrict__", "_Noreturn", "_Безвозврат", "float", "double",            "плав", "двойн", "typeof", "asm", "_Thread_local", "__thread", "_Atomic",            "типоф", "асм", "__attribute__",        };        для (цел ш = 0; ш < размер(ключ_слова) / размер(*ключ_слова); ш++)            хеш_вставить(&карта, ключ_слова[ш], (пусто *)1);    }    возврат хеш_получить2(&карта, ток->loc, ток->len);}

Особняком стоят логические идентификаторы:

#ifndef _STDBOOL_H_INCLUDED#define _STDBOOL_H_INCLUDED// Тип булевой переменной (встроенный тип _Bool)#define bool _Bool#define true 1#define false 0// Тип булевой переменной (русский синоним)#define бул _Bool#define истина 1#define ложь 0#define __bool_true_false_are_defined 1#endif // _STDBOOL_H_INCLUDED

Такой подход дает самое интересное — обратную совместимость с оригинальным языком.

Обратная совместимость как ключевая фича

Важно понимать: Руси — это не “новый язык”. Это надстройка над C. Всё, что работает в C, работает и в Руси. Это означает, что:

  • Можно копировать любой английский код — он скомпилируется без изменений.

  • Можно импортировать любые C-библиотеки — они будут работать как родные.

  • Можно смешивать int main() и цел главн() в одном файле.

  • Можно постепенно переходить на русский синтаксис, не переписывая весь проект сразу.

Это не замена английского. Это расширение выбора. Ты можешь писать так, как тебе удобно, не теряя совместимости с миром. Это свобода. Это комфорт. Это уважение к себе и своей культуре.

Целевая архитектура

Компилятор генерирует код для архитектуры x86-64 (AMD64) под операционные системы семейства Linux. Это означает, что:

  • Исполняемые файлы собираются в формате ELF64.

  • Используется стандарт System V AMD64 ABI (тот же, что у GCC и Clang).

Исходный код может иметь расширения .c, .си, .рс (программа на русском C). Заголовочные файлы могут иметь расширения .h, (заголовок на русском).

Самосбор

Компилятор способен скомпилировать сам себя. Это важный этап разработки, который подтверждает, что компилятор достаточно зрелый и надёжный для работы с собственным кодом.

Процесс самосбора выглядит так:

  1. Исходный код компилятора уже написан на Rusi (смесь русского и английского синтаксиса). Собрать его с помощью gcc не выйдет.

  2. Этот код компилируется текущей собранной версией компилятора (stage1).

  3. Полученный бинарник (stage2) снова компилирует исходный код (stage3).

  4. Проверяется, что stage2 и stage3 идентичны.

Скачать, собрать и проверить

Клонируем репозиторий:

git clone https://кодыч.рф/administrator/rusi.gitcd rusi

Собираем компилятор:

make

Готовый бинарник появляется в папке stage2/:

ls stage2/rusi.elf

Теперь можно заменить бинарник в корне проекта и прогнать тесты:

make test-all

Далее можно поупражняться в написании простеньких программ.
Создадим два файла: привет.з и программа.си. Можно и одним обойтись и проще сделать, но тут пример написан для красоты синтаксиса.

// привет.з - заголовочный файл#включить <stdio.h>#define ПЕЧАТЬ printf
// программа.си - основная программа#включить "привет.з"цел главн() {    ПЕЧАТЬ("Привет, мир!\n");    возврат 0;}

Компилируем и запускаем:

./rusi.elf программа.си -o программа./программа# Вывод: Привет, мир!

Список синонимов

📌 Типы данных (13 синонимов)

Английский

Русский

int

цел

char

символ

short

корот

long

длин

void

пусто

float

плав

double

двойн

struct

структ

union

объед

enum

переч

_Bool

бул

unsigned

беззнак

signed

знак

📌 Ключевые слова (17 синонимов)

Английский

Русский

typedef

типдеф

static

стат

extern

внеш

inline

инлайн

const

конст

volatile

летуч

auto

авто

register

регистр

restrict

огранич

_Alignas

_Выравнивание

_Alignof

_Выравнивание_типа

_Noreturn

_Безвозврат

_Generic

_Обобщ

_Atomic

_Атом

_Thread_local

_Поток_лок

__thread

__поток

📌 Операторы (18 синонимов)

Английский

Русский

return

возврат

if

если

else

иначе

for

для

while

пока

do

делай

switch

выбор

case

случай

default

умолч

goto

перейти

break

прерви

continue

продолжи

sizeof

размер

typeof

типоф

asm

асм

📌 Директивы препроцессора (15 синонимов)

Английский

Русский

#include

#включить

#include_next

#включить_след

#define

#определить

#undef

#отменить

#if

#если

#ifdef

#еслиопр

#ifndef

#еслинеопр

#elif

#иначеесли

#else

#иначе

#endif

#конецесли

#line

#строка

#error

#ошибка

#pragma

#прагма

defined

определено

once

однократно

📌 Встроенные макросы (9 синонимов)

Английский

Русский

__FILE__

__ФАЙЛ__

__LINE__

__ЛИНИЯ__

__func__

__функ__

__FUNCTION__

__ФУНКЦИЯ__

__COUNTER__

__СЧЕТЧИК__

__TIMESTAMP__

__ВРЕМЯ_ФАЙЛА__

__DATE__

__ДАТА__

__TIME__

__ВРЕМЯ__

__BASE_FILE__

__БАЗОВЫЙ_ФАЙЛ__

📌 Функции

Английский

Русский

main

главн

Вместо заключения

У нас очень развито направление перевода медиа-контента. Игры, книги, песни, фильмы и сериалы.

Переложить импортное кино на великий и могучий может любой переводчик, который всех категорически приветствует. Это не требует понимания того, как устроена кинокамера или монтажный стол. Напереводить так можно очень много всего.

Перевести игру может, теперь вообще кто угодно. Причем автоматизированно.

Перевести язык программирования — это совсем другое. Это сложно. Потому что перевести язык программирования — это иногда близко к пересозданию этого языка с нуля. Изначально я и начал создавать свой KVS с нуля.

Тут не получится просто взять и заменить «if» на «если» в исходниках компилятора. Потому что компилятор — это не текст. Это сложная система, состоящая из множества взаимосвязанных компонентов: лексер, парсер, препроцессор, генератор кода, оптимизатор, линкер. Каждый из этих компонентов ожидает определённые токены, определённые ключевые слова, определённую грамматику. И чтобы изменить язык, который понимает компилятор, нужно изменить все эти компоненты. А чтобы изменить их — нужно понять, как они работают.

Перевод фильма — это работа переводчика. Перевод компилятора — это работа инженера.

P.S.

Название «Руси» намеренно выбрано таким, чтобы было понятно, что это русифицированный Си. Использование латинского варианта названия «Rusi» — это Rui Ueyama R.U. + Си.

Ссылки

Планы развития

  • Переписать весь код компилятора на русский синтаксис.

  • Расширение для подсветки синтаксиса в VS Code / VS Codium.

  • Русификация стандартной библиотеки (стандарт.з, строки.з, математика.з).

  • Портирование КВС с Python на Руси.

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