Предисловие
На крайней (на дату написания) лекции Алексея Недори он высказал мысль, что он считает что если на вашем нечто можно написать факториал - это язык программирования. Что же... я теперь могу считать что у меня есть ещё один язык программирования!
Но прежде чем выпендриваться давайте я про него немного расскажу.
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 и станет интересно!
Ссылки
Почитать про "Рапиру" можно в брошюре советского времени
Почитать про "Робик" можно тут
Мой личный блог, пост из которого послужил публикацией к этой статье
