Блог типа

Не Всё Так Просто

Довольно быстро выяснилось, что, так скажем, обобщить парсер невозможно. Есть возможность выделить в парсерах похожих языков некоторые одинаковые части, но на этом обобщение и закончится. Потому что даже разбор выражений зависит от определенной конкретной грамматики. Беглый поиск не дал информации о каком-то способе обобщения любых выражений (expressions) хотя бы арифметики. И это 2015-й год. Каждый лепит что-то своё, у кого OR, у кого ||, у кого or, у кого ^, у кого **, =, ->, :=, и так далее, про целочисленное деление вообще молчу. Позорище вселенского масштаба. Никакой возможности обобщить и унифицировать.

Не зря Н. Вирт не стеснялся дублировать код компилятора от проекта к проекту, раз за разом, жестоко нарушая принцип DRY, чем очень злил вчерашних школьников, угоревших по всяким красивым принципам, манифестам, высерам Макконелла и так далее.

Ну ничего, даже в варианте с частичным обобщением уже нарисовался один важный паттерн потоковой обработки текста исходника. Его я и описал в соответствующем интерфейсе. Ещё из того, что я помню из разработки компиляторов, всегда было костыльно-проблематично описать результат работы компилятора.

У Вирта это был непосредственно генератор маш.кодов, минимальный объект с кучей полей, сразу видно, автор выделил минимум и его держался. У меня это превратилось в набор билдеров и холдеров, билдер содержит непосредственный результат разбора исходника и производит внутренние трансформации, холдер принимает на хранение результат работы билдера, часто это AST и вспомогательные индексы переменных, полей, параметров и модулей. Довольно странная структура, возможно, она тоже никак не обобщается.

В итоге получится инструментальный набор юного компиляторщика, не иначе.

Такая Мысль

Так как прошлые язычки в основном про алгоритмы (декларативные/императивные), и только чуть-чуть про данные (в виде dom), то логично предположить, что надо расширить эксперименты на системы типов и прочее.

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

В Оберонах выбран путь классического наследования. В КП добавлены абстрактные и полу-абстрактные типы, разделение доступа к методам при переопределении. В go вообще наследования нет, как такового. Множество вариантов.

Отдельным пунктом можно отметить псевдонимы и/или расширение пользователем примитивных типов, базовых сложных типов.

Так же встаёт вопрос о необходимости встроенных в язык сложных типов данных, типа MAP в LEAF.

Если программы, это алгоритмы + структуры данных, и с алгоритмами мы уже разобрались, надо переходить к структурам данных.

Решил начать издалека и как-то обобщить предыдущий опыт разработки компиляторов в виде универсального сканнера/полупарсера для будущего проекта.

Непонятно

Сильно интересный вопрос, какой выбрать язык и платформу для реализации очередного язычка, а заодно и опять думаю, надо ли вообще этим заниматься…

Вопрос не праздный.

Очередной Язычок

И снова от безделья появляются идеи написать очередной (да) язык. Оглядев LEAF, LOMO и o.t. для себя понял, что многие идеи хоть и звучат разумно, но выглядят при этом совершенно отвратительно.

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

Call(x: 1, y <- my.y, res -> x)

Сложновато, как не крути. Получается, что порядок аргументов процедуры важен. Из этого вытекает необходимость в сигнатуре процедуры описывать всё именно так, как в Обероне PROCEDURE Call(x: INTEGER; IN y: INTEGER; OUT x: INTEGER), что конечно неплохо (в очередной раз удивляюсь продуманности Оберона), но не оставляет пространства для маневра. И еще в этой форме записи всего полшага остаётся для признания процедуры функцией, со всеми вытекающими последствиями.

И таких моментов много. Навигация внутри MAP требует постоянного разыменования значения. Так как цепочки селекторов я не реализовал, навигация требует ещё и кучи временных переменных.

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

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

А тут ещё и язык шаблонов сам собой нарисовался. Простой способ описывать всякие объекты, произвольной природы, но при этом человекотипизированные (то есть человек понимает семантику чисто из описания, из текстового представления шаблона объекта). Интересные возможности открываются.

При этом недостатков в o.t. тоже хватает, например, необходимость закрывающего элемента, обозначающего конец содержимого. Никак я не придумал способ обнаруживать конец содержимого автоматически. Так же плохой идеей было переиспользование объектов. Точнее, идея хорошая, но по факту получилось слишком сложно, как в разборе, так и в понимании. А еще я не реализовал программирование внутри шаблонов, не осилил. Отсюда, в общем, и мысли о новом язычке, в котором, ну правда-правда, всё будет идеально продумано и реализовано.

Главное не перечитывать пост про жизненный цикл программы, а то можно до конца года так и сидеть, продумывая каждый этап.

В общем, пройдя по пути создания императивного языка, декларативного языка и языка разметки я снова оказался в начале пути.