Предисловие

На крайней (на дату написания) лекции Алексея Недори он высказал мысль, что он считает что если на вашем нечто можно написать факториал - это язык программирования. Что же... я теперь могу считать что у меня есть ещё один язык программирования!

Но прежде чем выпендриваться давайте я про него немного расскажу.

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 параметры

Предлагаю посмотреть на пару примеров и оценить самому:

  1. Кортежи:

ДЕРЕВО := <* "вопрос", <* "да-ветка" *>, <* "нет-ветка" *> *>
вывод: ДЕРЕВО[0]
\ => вопрос
вывод: ДЕРЕВО[1]
\ => <* да-ветка *>
вывод: ДЕРЕВО[1][0]
\ => да-ветка
  1. Срезы (вид на оригинальный кортеж или текст!)

проц ТЕКСТ_ПО_СЛОВАМ (ТЕКСТ)      \▪ печать текста по словам
  ТЕКСТ:= ТЕКСТ + " "             \▪ — это для обработки
                                   \ последнего слова
  пока ТЕКСТ /= "" цикл
    К:= индекс(" ", ТЕКСТ)        \ номер первого вхождения
                                   \ " " в ТЕКСТ (или 0)
    если К /= 1 то
      вывод: ТЕКСТ[:К-1]          \ "аб вг д"[:2] = "аб"
    все
    ТЕКСТ:= ТЕКСТ[К+1:]           \ "аб вг д"[4:] = "вг д"
  кц                              \ "аб вг д"[4:5] = "вг"
конец
ТЕКСТ_ПО_СЛОВАМ
    ("Каждый коротышка был ростом с небольшой огурец")
◆ Каждый
◆ коротышка
◆ был
◆ ��остом
◆ с
◆ небольшой
◆ огурец

"Рапира". Возрождаем

Итак, проект я разделил на две фазы:

  1. Имплементация оригинального языка с возможными но минимальными изменениями

  2. Модернизация языка с ожидаемым большим количеством изменений

Фаза 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. Предварительно хочется конечно воспользоваться характером языка - динамическая типизация, простота синтаксиса.

Но важнее, что требуется решить следующие вопросы при модернизации:

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

  2. Таргет генерации кода - этот вопрос ставится и на первой фазе и принято решение на данном этапе генерировать код на языке Си, это позволит:

  • Упростить разработку

  • Иметь легко распространяемый генерируемый код

  • Заиметь потенциал к хорошей производительности языка, избежав преграды интерпретации

  • Открыть дверь к прямому взаимодействию с Си кодом, позволив использовать большое количество готовых библиотек

  1. Отсутствие ООП/структур/именованых кортежей - в языке присутствуют нетипизированные контейнеры - кортежи, которые могут выступать как структуры, но так как именованных элементов нет - стоит задуматься о добавлении такого конструкта, а также об организации взаимодействия таких объектов, будь это ООП или что-то иное (наследование и т.п.)

  2. Модульная система - в препринте мало сказано о модульной системе, стоит задуматься об организации оной

  3. Система сборки и организации зависимостей - встроенная система сборки и работы с зависимостями позволит избежать участь соревнования различных таковых систем сделанных пользователями, можно посмотреть на истории этого всего в Python и проблемы миграции с одной системы на другую

  4. ПИВИС/REPL - добавить возможность работы с языком в стиле: прочитать - исполнить - вычислить - и-снова, в стиле интерпретируемых языков

  5. Управление памятью

ООП?

Позволю себе также написать несколько крайней сырых идей, которые у меня есть по поводу реализации ООП/системы сущностей/объектов/... в языке.

Итак, обратимся к брошюре, там мы видим вот такую интересную штуку:

### 1.6. Модули и устройства

Планируемые возможности аппарата модулей и устройств:
- подключение модуля пополняет список стандартных имен (в том числе имен стандартных процедур и функций);
- подключение модуля пополняет набор простых предписаний (т.е. языковыми средствами создаются исполнители, аналогичные существующим в Робике [1]);

Подождите-ка, а что такое эти самые "исполнители"? В языке предшественнике нашей Рапире был вот такой интересный конструкт. Для разнообразия и чтобы вы прониклись эстетикой прикреплю на этот раз картинку:

Из доки Робика
Исполнители в Робике

Ничего не напоминает? А мне вот очень напоминает что-то типо акторов в Erlang, например. Пока я не буду раскачивать эту идею дальше, просто потому что сам знаю позорно мало про акторов, поэтому оставим на Фазу 2.

Послесловие

Не стоит думать что это какой-то слишком серьёзный проект - мотивация тут это желание изучить что-нибудь новое, проверить концепт возрождения. Так как автор не мастак в разработке ЯП, то относиться к коду/результату советую скептически.

Следите за обновлениями, когда-нибудь я доберусь до Фазы 2 и станет интересно!

Ссылки