Теперь ЭТО язык программирования! “Рапира”.
Предисловие
На крайней (на дату написания) лекции Алексея Недори он высказал мысль, что он считает что если на вашем нечто можно написать факториал - это язык программирования. Что же… я теперь могу считать что у меня есть ещё один язык программирования!
Но прежде чем выпендриваться давайте я про него немного расскажу.
P.S. Если вам не интересно слушать мои мысли про советские ЯП то переходите к главе “Рапира”. Что это?
Языковая археология
Существует прекрасный цикл презентаций от Петра Советова, который называется “Автоматизация программирования в СССР”. Материал раскрывает крайне интересную тему развития и забытых идей в области компиляторов и трансляторов в СССР, вопреки моему (наивному) предварительному ощущению - многие из тех техник и подходов что были описаны оказались либо мне не знакомыми, либо смотрели на привычные алгоритмы с другого взгляда, но ближе к бизнесу.
К сожалению мы опрометчиво выкинули (по большей части) историю разработок в языках программирования в нашей стране через левое плечо. Но ведь языки программирования, как и любые проекты, возникают только если строить на плечах гигантов - прошлых проектов, а плечи наших соотечественников нам то гораздо ближе. Развитие идей падших, ну или ещё не падших но прошедших испытание временем, языков программирования позволило создать те языки которыми мы с вами и пользуемся.
Однако такое развитие не происходит в вакууме, для возникновения того или иного языка в неком определённом его виде должны быть ограничители и потребности оных - специальное железо, социальная/политическая потребность, открытия и идеи локальных университетов и лабораторий. Безусловно никто не мешает основываться на открытиях и идеях “мейнстрим” ЯП, все из которых не являются нашими разработками, более того вы должны это делать, однако в этом процессе абсолютно забывается локальная мысль, идея, история, которая на самом деле может сильно повлиять на те самые запросы и потребности и дать очень свежие и необычные мысли для создания ЯП.
По моему мнению необходимо возрождать и модернизировать те идеи и проекты, которые были сделаны до нас, продолжать нить идей.
Сказав всё это, хочется добавить, что пусть этим занимаются конечно умные люди - профессионалы своего дела, которых нам не занимать. Я в эту кагорту людей не вхожу, но идея есть идея, почему бы и не попробовать?
“Рапира”. Что это?
Наконец-то давайте про язык. Был значит вот такой вот язычок - “Рапира”. Довольно простенкий, с русским синтаксисом, использовался, как это и полагается, для обучения программированию.
Посмотрим на первую версию языка:
ПРОЦ СЧАСТЬЕ;
(****************************************)
(* Расчет количества счастливых билетов *)
(****************************************)
ИМЕНА: КОЛ_СУММ, I, J, К, КОЛИЧЕСТВО ;
ФКОРТ (28,0) -> КОЛ_СУММ ;
ДЛЯ I ОТ 0 ДО 9 ::
ДЛЯ J ОТ 0 ДО 9 ::
ДЛЯ К ОТ 0 ДО 9 ::
КОЛ_СУММ [I+J+К+1] + 1 -> КОЛ_СУММ [I+J+К+1]
ВСЕ
ВСЕ
ВСЕ;
0 -> КОЛИЧЕСТВО;
ДЛЯ I ОТ 1 ДО 28 ::
КОЛИЧЕСТВО + КОЛ_СУММ [I] ** 2 -> КОЛИЧЕСТВО
ВСЕ;
ВЫВОД: "Всего есть",КОЛИЧЕСТВО," счастливых билетов"
КНЦ (* процедуры СЧАСТЬЕ *);
И тут у вас наверняка что-то сжалось внутри, да… сейчас с высоты наших растов и питонов это кажется несколько смешным. Но не спешите уходить! У меня есть для вас и другая версия языка, из брошюры, которая описывала обновлённую версию языка:
проц ШИВОРОТ_НАВЫВОРОТ () \▪ печать текста наоборот
вывод: "Введите текст" \▪ приглашение ко вводу
ввод текста: ТЕКСТ \ запрос текста в диалоге
НАОБОРОТ:= "" \ пустой текст
для К до #ТЕКСТ цикл \ от 1
НАОБОРОТ:= ТЕКСТ[К] + НАОБОРОТ \ #"огурец" = 6
кц \ "огурец"[3] = "у"
вывод: НАОБОРОТ \ вывод результата на экран
конец
вызов ШИВОРОТ_НАВЫВОРОТ()
◆ Введите текст
огурец
◆ церуго
Уже полегчало? Должно было! Ну по крайней мере мы выдыхаем, видя что
присваивание это :=. Да, тут вы скажете что “ну дак мы
этот Паскаль уже проходили старичок, это уже старьё”. Не спорю и
даже согласен, но посмотрите на это так: у языка уже есть несколько
“версий” - так давайте сделаем ещё одну?
Вот этим я и собирался заниматься
Кратко - что есть максимально интересного в языке:
- Динамическая типизация
- Срезы (a.k.a slice, см. Zig/Rust/…)
- Очень простой базис языка, к которому всё сводится
- Динамический скоупинг
- In/Out параметры
Предлагаю посмотреть на пару примеров и оценить самому:
- Кортежи:
ДЕРЕВО := <* "вопрос", <* "да-ветка" *>, <* "нет-ветка" *> *>
вывод: ДЕРЕВО[0]
\ => вопрос
вывод: ДЕРЕВО[1]
\ => <* да-ветка *>
вывод: ДЕРЕВО[1][0]
\ => да-ветка
- Срезы (вид на оригинальный кортеж или текст!)
проц ТЕКСТ_ПО_СЛОВАМ (ТЕКСТ) \▪ печать текста по словам
ТЕКСТ:= ТЕКСТ + " " \▪ — это для обработки
\ последнего слова
пока ТЕКСТ /= "" цикл
К:= индекс(" ", ТЕКСТ) \ номер первого вхождения
\ " " в ТЕКСТ (или 0)
если К /= 1 то
вывод: ТЕКСТ[:К-1] \ "аб вг д"[:2] = "аб"
все
ТЕКСТ:= ТЕКСТ[К+1:] \ "аб вг д"[4:] = "вг д"
кц \ "аб вг д"[4:5] = "вг"
конец
ТЕКСТ_ПО_СЛОВАМ
("Каждый коротышка был ростом с небольшой огурец")
◆ Каждый
◆ коротышка
◆ был
◆ ростом
◆ с
◆ небольшой
◆ огурец
“Рапира”. Возрождаем
Итак, проект я разделил на две фазы:
- Имплементация оригинального языка с возможными но минимальными изменениями
- Модернизация языка с ожидаемым большим количеством изменений
Фаза 1
За референс взята уже упомянутая брошюра, задача сделать имплементацию компилятора для языка из брошюры. Почему не интерпретатор? Тут я позволил себе немного свободы выбора, вообщем-то интерпретатор я уже делал совсем недавно, к тому же это труднее чем кое-что другое - компиляция в Си. Тут мы потенциально получаем неплохую скорость, а также открываем двери к интеропу с Си.
Итак, момент выпендрежа:
функ ФАКТОРИАЛ (Н)
если Н < 2 то
возврат 1
иначе
возврат Н * ФАКТОРИАЛ(Н - 1)
все
конец
вывод: ФАКТОРИАЛ (5)
\ => выводит 120
Теперь можно официально окрестить язык живым, я полагаю?
Одна из целей - это компилировать в читаемый Си код, судите конечно сами, вот вам пример во что компилируется программа выше (реальный вывод компилятора):
#include "runtime.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// функ ФАКТОРИАЛ
RAP_Object *RAP_FUNC_FAKTORIAL(struct RAP_CallFrame *_frame,
RAP_Object **_args, unsigned int _argc) {
RAP_Object *_local_N = _args[0];
RAP_Object *_t0 = RAP_create_int_obj(2);
RAP_Object *_t1 = RAP_less_than(_local_N, _t0);
if (_t1->logical_val) {
RAP_Object *_t2 = RAP_create_int_obj(1);
return _t2;
} else {
RAP_Parameter *_p0 = RAP_create_parameter(RAP_PARAMETER_MODE_IN, "Н");
RAP_Object *_t3 = RAP_create_callable_obj(_frame, &RAP_FUNC_FAKTORIAL, &_p0, 1);
RAP_Object *_t4 = RAP_create_int_obj(1);
RAP_Object *_t5 = RAP_subtract(_local_N, _t4);
RAP_Object *_t6 = RAP_call_callable_obj(_t3, &_t5, 1);
RAP_Object *_t7 = RAP_multiply(_local_N, _t6);
return _t7;
}
}
int main(void) {
struct RAP_CallFrame _main_frame = {NULL, NULL, 0};
RAP_Parameter *_p1 = RAP_create_parameter(RAP_PARAMETER_MODE_IN, "Н");
RAP_Object *_t0 = RAP_create_callable_obj(&_main_frame, &RAP_FUNC_FAKTORIAL, &_p1, 1);
RAP_Object *_t1 = RAP_create_int_obj(5);
RAP_Object *_t2 = RAP_call_callable_obj(_t0, &_t1, 1);
char *_s0 = RAP_stringify_object(_t2);
printf("%s", _s0);
printf("\n");
return 0;
}Думаю идея устройства runtime компилятора понятно из
сниппета выше, и она довольно простая, всё - это объект:
typedef enum {
RAP_OBJECT_TAG_NULL,
RAP_OBJECT_TAG_LOGICAL,
RAP_OBJECT_TAG_CALLABLE, // unifies proc and func
RAP_OBJECT_TAG_INT,
RAP_OBJECT_TAG_FLOAT,
RAP_OBJECT_TAG_TEXT,
RAP_OBJECT_TAG_TUPLE,
RAP_OBJECT_TAG_SLICE,
} RAP_ObjectTag;
typedef struct {
RAP_ObjectTag tag;
union {
bool logical_val;
int64_t int_val;
double float_val;
struct RAP_Tuple *text_val;
struct RAP_Tuple *tuple_val;
struct RAP_Callable *callable_val;
struct RAP_Slice *slice_val;
};
} RAP_Object;Если интересно узнать глубже приглашаю по ссылке в репозиторий см. Ссылки
Фаза 2
По большей части свои идеи я оставил на момент когда будет готова Фаза 1. Предварительно хочется конечно воспользоваться характером языка - динамическая типизация, простота синтаксиса.
Но важнее, что требуется решить следующие вопросы при модернизации:
- Синтаксис - по личному мнению автора, для языка с кириллицей надо брать питоно-подобный синтаксис на отступах, чтобы не переключать раскладку и ставить скобочки, плюс это сохранит простоту синтаксиса
- Таргет генерации кода - этот вопрос ставится и на первой фазе и принято решение на данном этапе генерировать код на языке Си, это позволит:
- Упростить разработку
- Иметь легко распространяемый генерируемый код
- Заиметь потенциал к хорошей производительности языка, избежав преграды интерпретации
- Открыть дверь к прямому взаимодействию с Си кодом, позволив использовать большое количество готовых библиотек
- Отсутствие ООП/структур/именованых кортежей - в языке присутствуют нетипизированные контейнеры - кортежи, которые могут выступать как структуры, но так как именованных элементов нет - стоит задуматься о добавлении такого конструкта, а также об организации взаимодействия таких объектов, будь это ООП или что-то иное (наследование и т.п.)
- Модульная система - в препринте мало сказано о модульной системе, стоит задуматься об организации оной
- Система сборки и организации зависимостей - встроенная система сборки и работы с зависимостями позволит избежать участь соревнования различных таковых систем сделанных пользователями, можно посмотреть на истории этого всего в Python и проблемы миграции с одной системы на другую
- ПИВИС/REPL - добавить возможность работы с языком в стиле: прочитать - исполнить - вычислить - и-снова, в стиле интерпретируемых языков
- Управление памятью
ООП?
Позволю себе также написать несколько крайней сырых идей, которые у меня есть по поводу реализации ООП/системы сущностей/объектов/… в языке.
Итак, обратимся к брошюре, там мы видим вот такую интересную штуку:
### 1.6. Модули и устройства
Планируемые возможности аппарата модулей и устройств:
- подключение модуля пополняет список стандартных имен (в том числе имен стандартных процедур и функций);
- подключение модуля пополняет набор простых предписаний (т.е. языковыми средствами создаются исполнители, аналогичные существующим в Робике [1]);
Подождите-ка, а что такое эти самые “исполнители”? В языке предшественнике нашей Рапире был вот такой интересный конструкт. Для разнообразия и чтобы вы прониклись эстетикой прикреплю на этот раз картинку:

Ничего не напоминает? А мне вот очень напоминает что-то типо акторов в Erlang, например. Пока я не буду раскачивать эту идею дальше, просто потому что сам знаю позорно мало про акторов, поэтому оставим на Фазу 2.
Послесловие
Не стоит думать что это какой-то слишком серьёзный проект - мотивация тут это желание изучить что-нибудь новое, проверить концепт возрождения. Так как автор не мастак в разработке ЯП, то относиться к коду/результату советую скептически.
Следите за обновлениями, когда-нибудь я доберусь до Фазы 2 и станет интересно!
Ссылки
- Почитать про “Рапиру” можно в брошюре советского времени
- Почитать про “Робик” можно тут
- Репозиторий компилятора