image


Недавно прочитал на Хабре статью Свой язык, или как я устал от ассемблера и С, и невольно взглядом зацепился за один абзац:


Я решил не сильно париться, поэтому использовал библиотеку parglare. Она очень легкая и удобная, всем рекомендую. Для описания синтаксиса парсер принимает строку в соответствующем формате, использует регулярные выражения (не надо осуждать регулярки, они всесильны!).

В результате решил опубликовать статью на основе своих старых записей еще с тех времен, когда идея NewLang до конца еще не выкристаллизовалась, но уже хотелось писать реальный код и тестировать разные концепции.


Ведь в жизни практически любого программиста может наступить момент, когда ему в голову приходит светлая идея — разработать свой собственный язык программирования. Может быть и не ради захвата мира, наравне с C/C++, Python или хотя бы PHP, а в качестве личного пет-проекта, с которым он, длинными зимними вечерами будет оттачивать собственное мастерство.


А так как у любого языка (не только программирования), все начинается с анализа его грамматики, то самой первой задачей создателя будет выбор инструментов для синтаксического анализа исходного текста.


Это история — заметки на память о муках выбора связки лексер-парсер для разбора грамматики NewLang. А так же попытка описать и систематизировать выводы об особенностях разных анализаторов с которыми пришлось поработать при выборе парсера для разбора грамматики у своего языка программирования.


Используемые термины.


Чтобы было понятно, о чем в дальнейшем пойдет речь.


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

Парсер — на основе последовательности токенов выполняется синтаксический анализ, например строит абстрактное синтаксическое дерево (AST).

Заход № 1 — Flex + Bison


GitHub — westes/flex: The Fast Lexical Analyzer — scanner generator for lexing in C and C++
Bison — GNU Project — Free Software Foundation


После прочтения умных книжек я начал с классики Flex + Bison. Это старые и давно отработанные приложения с широчайшими возможностями по настройке с помощью которых можно описать синтаксис и получить исходные файлы лексера и парсера для языка практически с любой грамматикой.


К сожалению, у этих старичков очень большой порог входа и тяжелая наследственность. Чего только стоит определение собственного класса лексера через #define, а так же отсутствие нормальной поддержки С++. Вынужденные танцы с бубном для пасинга отдельной строки, когда не требуется анализировать файл целиком и прочие не всегда очевидные проблемы и разные не понятные условности.


Другими словами, через несколько недель безуспешных мучений я решил посмотреть альтернативы, а так как начальный файл лексики после экспериментов с Flex + Bison кое-как уже был сделан, то следующая связка была Flex + lemon.


Заход № 2 — Flex + Lemon


The Lemon LALR(1) Parser Generator


Тут все оказалось до примитивности просто и понятно. Реально очень быстрый старт с боевыми примерами, очень наглядный и понятный способ записи правил (по сравнению с Bison). Все хорошо кроме одного, хорошее быстро заканчивается, если приходится анализировать более одной строки.


По сути, Lemon с Bison, это как Инь и Янь. Lemon простой и удобный для работы с одной строкой (для этого он и создавался), а Bison супер-пупер-мега комбайн для парсинга файлов любых размеров.


Поэтому, поиски связки лексер + парсер продолжились и после прочтения очередной статьи, что парсеры языков можно делать на регулярках, решил посмотреть, что есть в этом направлении:


Заход № 3 парсер на регулярках re2c


Из описания re2c:


По сути, на этой штуке можно писать лексические анализаторы прямо на коленке за несколько минут.

    /*!re2c
        re2c:define:YYPEEK       = "*cursor";
        re2c:define:YYSKIP       = "++cursor;";
        re2c:define:YYBACKUP     = "marker = cursor;";
        re2c:define:YYRESTORE    = "cursor = marker;";
        re2c:define:YYBACKUPCTX  = "ctxmarker = cursor;";
        re2c:define:YYRESTORECTX = "cursor = ctxmarker;";
        re2c:define:YYRESTORETAG = "cursor = ${tag};";
        re2c:define:YYLESSTHAN   = "limit - cursor < @@{len}";
        re2c:define:YYSTAGP      = "@@{tag} = cursor;";
        re2c:define:YYSTAGN      = "@@{tag} = NULL;";
        re2c:define:YYSHIFT      = "cursor += @@{shift};";
        re2c:define:YYSHIFTSTAG  = "@@{tag} += @@{shift};";
    */

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


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


После этого решил с ним не заморачиваться и посмотреть альтернативы классическим лексерам и парсерам.


Заход № 4 — flex (flexcpp) + bisoncpp


Тогда как у традиционных flex + bison поддержка С++ реализована через одно место на уровне — работает и не трогай, то решил посмотреть их альтернативную реализация flexcpp + bisoncpp с нативной поддержкой С++.


Первое впечатление было, то что доктор прописал!


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


В шаблонах правил bisoncpp не поддерживает юникод (хотя сам лексер-парсер с ним справляется на ура), и совсем не понятная ситуация с поддержкой. Как я понял, разработчиком является вроде бы один человек, но пообщаться с ним насчет ошибок при обработке русских символов так и не получилось.*


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




*) Через два года мой тикет был закрыт с комментарием, что поддерживаются только ascii символы.


Заход № 5 — неродной парсер ANTLR


От использования ANTLR (от англ. ANother Tool for Language Recognition — «ещё одно средство распознавания языков») — генератора нисходящих анализаторов для формальных языков, я решил сразу отказать из-за того, что он написан на Java,.


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


Таким образом я опять вернулся к старичкам Flex и Bison, с которых все и начиналось.


Заход № 6, последний — возвращение к Flex + Bison


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


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


Выводы на память


В итоге для себя решил следующее: если нужен простенький шаблонизатор, то идеальный вариант re2c (если почему-то не подходит regexp). Если требуется анализировать синтаксис сложнее обычных регулярок, но в одну строку, то идеальной будет связка flex+lemon, а если нужна серьезная артиллерия, то тут однозначно flex + bison.


От связки flexcpp + bisoncpp отказался совсем. Что с поддержкой — не понятно, синтаксис от классики отличается не очень сильно (хотя тоже нужно ломать голову), а обход выявленных косяков не стоят того синтаксического сахара.


И по итогам множества экспериментов с разными вариантами синтаксиса удалось сформулировать пару важных архитектурных моментов, которые могут очень сильно упростить жизнь создателям языком программирования:


Стратегия обработки ошибок синтаксиса


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


Но если грамматика языка очень сложная (привет C++), и её описание становится сложной задачей, то можно отказался и от анализа ошибок синтаксиса непосредственно в парсере! То есть, лучше сделать максимально широкую лексику (даже с теми вариантами, которые являются для языка ошибочными), но ловить эти ошибки уже при анализе AST!


В этому случае, поддерживать описание грамматики языка становится значительно проще (меньше синтаксических правил, проще их формальное описание и т.д.), а самое главное, при описании грамматики не нужно думать про lval или rval, где можно указывать ссылку, а где нет — т. е. можно указывать все и везде, а вот анализ допустимости использования конкретных терминов будет выполняться позднее при анализе AST.


Отказа от полного анализа грамматики языка на уровне лексера — парсера и перенос проверки корректности синтаксических конструкций на этап разбора синтаксического дерева позволяет в разы, если не на порядки сократить и значительно упростить описание грамматических правил!


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


Макросы и модификация грамматики в Runtime


Какими бы мощными не были flex+bison, но у этой связки есть одна архитектурная проблема. Логика flex и bison построена на конечных автоматах и изменить грамматику языка во время выполнения приложения невозможно, тем более Bison сам вызывает лексер для получения очередной порции данных и ему очень непросто подсунуть измененные данные прямо во время работы. А так хотелось сделать возможность раскрытия макросов и модификации синтаксиса за один проход анализатора!


Для этого пришлось переделать логику работы flex+bison, чтобы парсер получал данные из лексера не напрямую из yylex, а через функцию — прокси. Эта промежуточная функция, складывает считанные лексемы во внутренний буфер. Данные в буфере анализируется на предмет наличия макросов и только после их раскрытия, лексемы отдаются в парсер из вершины буфера по одной за раз. Подробнее о макросах NewLang можно почитать тут.


Самое главное при разработке грамматики!


Но самый важный совет мне подсказал друг, который некогда участвовал в проекте по разработке парсера для языка программирования. И в правильности его совета — пиши тесты для грамматики — я убеждался уже множество раз. Даже так, ПИШИ ТЕСТЫ ДЛЯ ГРАММАТИКИ.


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


И удачи всем языкописателям!


Комментарии (26)


  1. eugenk
    00.00.0000 00:00
    +6

    Прошу прощения, сразу бросилось в глаза

    От использования ANTLR (от англ. ANother Tool for Language Recognition — «ещё одно средство распознавания языков») — генератора нисходящих анализаторов для формальных языков, я решил сразу отказать из-за того, что он написан на Java. В этом нет никаких религиозных предпочтений, просто голый расчет и отсутствие желания использовать JRE только для парсера, когда были еще варианты.

    Собственно почему ??? ANTLR прекрасно умеет генерировать код и на С, и на питоне и на джаваскрипте и ещё на куче всего. По-моему оптимальный выбор.


    1. rsashka Автор
      00.00.0000 00:00

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


      1. eugenk
        00.00.0000 00:00
        +1

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


        1. rsashka Автор
          00.00.0000 00:00

          Да, все нормально.
          В последствии от идеи изменять грамматику языка все равно я решил отказаться в пользу реализации системы макросов, но смысла менять flex+bison на что-то другое уже не было.


        1. rsashka Автор
          00.00.0000 00:00

          И спасибо за вопрос, уточнил этот момент в статье.


    1. KvanTTT
      00.00.0000 00:00
      +2

      Ну если быть точным, то на C++, а не на C.


  1. michael_v89
    00.00.0000 00:00
    +1

    Все описанные выше эксперименты заняли в общем итоге несколько месяцев

    Мой совет, не используйте все эти flex/bison, а напишите вручную простой нисходящий парсер без отдельного лексера. При таком подходе фактически лексемами будут отдельные символы. У меня это заняло 2 недели, и поддержка UTF-8 получилась автоматически.


    1. rsashka Автор
      00.00.0000 00:00

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


    1. slonopotamus
      00.00.0000 00:00
      +2

      На связке flex+bison тоже никто не мешает перенести всю работу на bison, так чтобы flex просто выплёвывал буковки по одной. Принципиальная разница между bison и самодельным top-down парсером в том что бизон будет на вас орать увидев неоднозначные конструкции, а top-down будет их молча КАК-ТО парсить полагаясь на порядок веток кода, который далеко не факт что обдумывался, а не получился спонтанно в процессе добавления ветвей.


      1. michael_v89
        00.00.0000 00:00
        +1

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


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


        1. slonopotamus
          00.00.0000 00:00
          +2

          Особых отличий от кода нет, тут передвинули правило, там вызов функции.

          В Bison'е приоритеты работают не так. Вы должны пойти и в явном виде написать %prec. Я подчёркиваю, это осознанное и умышленное действие. А не "ну вот как-то я ветки кода расположил и в результате там сидит незаметная невооружённым взглядом неоднозначность, которая втихаря резолвится порядком веток".


  1. Refridgerator
    00.00.0000 00:00
    +1

    Ведь в жизни практически любого программиста может наступить момент, когда ему в голову приходит светлая идея — разработать свой собственный язык программирования
    Прямо сейчас борюсь с непреодолимым желания сделать то же, ориентированное на математические вычисления. За 30 лет накопилось слишком много идей, что можно сделать лучше и как именно. Но решил, что сначала нужно придумать крутое название. Варианты типа «NewSuperLang» выглядят не очень убедительно)


    1. rsashka Автор
      00.00.0000 00:00

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


      1. Refridgerator
        00.00.0000 00:00

        Да не вопрос) Навскидку:

        Компилировать не в машинный код, а в c++ или c#. Сильно облегчает сложность а заодно позволяет использовать в качестве скриптов из уже существующих проектов.

        Снизить количество круглых и фигурных скобок, с чем у c++ и c# явный перебор. А квадратных скобок наоборот побольше, визуально они хорошо выделяются из сплошного текста.

        Поддержка постфиксной/префксной записи, чтобы вместо
        t = log(cosh(x));
        можно было писать
        t = log<<cosh<<x;
        или
        x>>cosh>>log>>t;

        вариант с двумя переменными:
        x = cos<<arctan<<x,y;
        И чтобы так тоже можно было:
        x,y = x+y,x-y;
        Ну то есть не обязательно перечисления оборачивать в круглые или фигурные скобки, если их границы очевидны.

        Поддержка инфиксной записи для произвольных символов, например вместо
        conjmul(a,b)
        в Вольфраме можно писать
        a~conjmul~b
        Но я бы левый и правый символ всё же сделал бы разными для наглядности, типа
        a]conjmul[b
        Естественно, с поддержкой односимвольных псевдонимов.

        Допускать неявное умножение, как у Вольфрама, типа
        t = 5(x+1)sin(x);
        Или даже так
        t = sin^3(x);
        а с нижним индексом например так
        t = ChebyshevT.5(x);

        Допускать другие символы для перечисления, чтобы мочь писать
        dt = 2023.01.01 11:12:13.777;
        tcp.connect<<127.0.0.1:5000;
        В переменную dt и метод connect придёт уже распарсенная строка древовидного типа. А также из этого следует, что запись 5.5 ещё не тип double, а может быть и fixed point, и rational, и кортеж из двух целых.

        Комплексные и дуальные числа как минимум должны быть. И не обязательно в формате a+i*b (алгебраическом), но и в векторном, например a:b:i, a`b`i или как-то ещё.

        Некомутативное умножение нужно, и отдельные символы для умножений из линейной алгебры;

        Массив как дискретная функция. Чтобы выхода за диапазоны не могло быть в принципе. Либо 0, либо по остатку от деления на длину массива, либо вызыв callback функции. А также иметь значение смещения, чтобы и с 1 нумеровать можно было.

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

        Возможность контроля времени выполнения цикла не отходя от кассы.

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

        Отдельный тип index для циклов и массивов, и никаких больше int! Кто получал бесконечный цикл с unsigned int, тот поймёт.

        Один символ # для комментариев как человеку, так и компилятору. Непонятные инструкции просто пропускаются. И никаких условных компиляций! В котёл тех, кто такое придумал. Не должна логика программы зависеть от ключей компиляции и #defin-ов, раскиданных неизвестно где.

        Более продвинутые switch-case конструкции для описания автоматной логики. Или даже отдельная абстракция для этого, как class у структур.

        Поддержка data-flow стиля, в DSP крайне полезная штука. Визуально выглядит так, наброски SDK для такого у меня уже имеются.


        1. rsashka Автор
          00.00.0000 00:00
          +1

          Как бы это не было странно, но большинство ваших предложений уже так или иначе реализовано или может быть реализовано с уже имеющимися возможностями языка:

          Компилятор языка делается именно как транспайлер на С++.

          Для снижения количества скобок пришлось специально ограничить систему встроенных типов (есть только тензоры в квадратных скобках и словари в круглых скобках), а от все остальные типы (tuple, enum и пр.) можно реализовать на базе этих двух.

          Присвоение нескольких значений сразу нескольким переменным уже давно сделано, т.е. можно записать и так: x,y = x+y,x-y; и x, dict = ... dict; В последнем случае из словаря в переменную переносится первый элемент.

          Комплексные числа заложены в планах, а для представления векторов наверно достаточно будет уже существующих тензоров.

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

          Любые дополнительные операции для своего DSL можно реализовать с помощью макросов.

          Выход из циклов или даже из вложенных функций тоже уже реализован

          Test0(arg) := { if($arg==0) return("DONE - 0"); «FAIL» };
          Test1(arg) := { if($arg==1) return("DONE - 1"); Test0($arg); };
          Test(arg) := {+ if($arg >= 0) Test1($arg); $arg; +};
          
          Test(0); # Вернет «DONE — 0» возврат из вложенной функции Test0
          Test(1); # Вернет «DONE — 1» возврат из вложенной функции Test1
          Test(2); # Вернет «FAIL» возврат из вложенной функции Test0
          Test(-2); # Вернет -2 — возврат из функции Test
          

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

          Отдельный тип для индексов не требуется, т. к. все числа знаковые, а отдельный тип (для без знаковых чисел отсутствует).

          Символ # уже используется для однострочных комментариев, в том числе и для того, чтобы можно было запускать программы как обычные скрипты

          Пример вычисления факториала 40 на NewLang

          #!../output/nlc --eval
            
          fact := 1\1; # Рациональное число без ограничений точности
          mult := 40..1..-1?; # Получить итератор для множителей от 40 до 2
          [mult ?!] <-> { # Цикл, пока не закончатся данные итератора
              fact *= mult !; # Множитель из текущего элемента итератора
          };
          fact # Вывести итоговый результат
          

          Вывод:

          815915283247897734345611269596115894272000000000\1
          

          Нет никаких условных компиляций и ifdef-ов!!!

          Вместо switch-case реализован оператор оценки выражений, это более продвинутая версия Pattern Matching, для которой можно указать, какой их операторов сравнения нужно использовать:

          • ==> — сравнение на равенство с приведением типов;

          • ===> — сравнение на точное равенство;

          • ~> — проверка типа (имени класса);

          • ~~> — утиная типизация;

          • ~~~> — строгая утиная типизация.

          Правда поддержку инфиксной записи для произвольных символов как Вольфраме в настоящий момент делать не планирую, т. к. очень сильно сбивает с толку, так же как и не явное умножение. Может быть это и здорово выглядит на бумаге, но для парсера это будет сущий ад и море ошибок. И я не понял, что имеется ввиду под «Поддержкой data-flow стиля»


          1. Refridgerator
            00.00.0000 00:00

            Компилятор языка делается именно как транспайлер на С++
            Конкретно для меня в приоритете именно c#.

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

            Отдельный тип для индексов не требуется, т. к. все числа знаковые
            Вам не требуется. Требуется мне) И не только чтобы к нему диапазон привязать, а дополнительную безопасность обеспечить и оптимизацию доступа.

            Вместо switch-case реализован оператор оценки выражений, это более продвинутая версия Pattern Matching
            Так я не о Pattern Matching говорил, а об автоматном программировании, когда есть принципиальная разница между switch(внутреннее состояние) и switch(внешние переменные), который это самое внутреннее состояние и меняет.

            Насчет контроля внешних переменных, не уверен, что такое вообще возможно
            Это возможно введением дополнительной языковой конструкции помимо if, например с названием check.

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

            ==> — проверка на равенство с приведением типов
            К слову, её нельзя выполнить однозначно. Равенство int64 c double может отличаться от того, к какому именно типу их приводить. Я бы запретил любые неявные сравнения.

            fact := 1\1;
            mult := 40..1..-1?;
            [mult ?!] <-> {fact *= mult !;};

            Не хотелось бы критиковать ваш в код. Но в чём здесь преимущество перед
            bigint x=1;
            for(k=2..n) x*=k;

            ?
            (А обратный слэш обычно для спец.символов зарезервирован, я бы не стал его занимать — мало ли, может и до латеха дело дойдёт).

            что имеется ввиду под «Поддержкой data-flow стиля»
            Это такой стиль программирования, когда вы не пишите, в каком порядке вызываются ваши функции. Вместо этого вы пишите, откуда и куда посылаются данные, в любом порядке. Таким образом получается ориентированный граф, который перед выполнением сортируется, а заодно и исследуется на наличие обратных связей и «мёртвых» объектов, которые не получают и не отдают данных. Также заодно можно выяснить (также полностью автоматически) что из этого можно выполнить параллельно. Все переменные хранятся непосредственно в памяти, что также снижает вероятность stack overflow.


  1. dprotopopov
    00.00.0000 00:00
    +1

    Забыли упомянуть классику - lex+yacc

    Хотя это в приципе переросло в flex+bison


    1. rsashka Автор
      00.00.0000 00:00
      +1

      Ну не то чтобы забыл, просто у меня не было цели, протестировать все возможные варианты. А как вы правильно заметили, классика переросла в flex+bison, с которой я начал и на которой все и закончилось.


  1. slonopotamus
    00.00.0000 00:00
    +2

    Bison сам вызывает лексер для получения очередной порции данных

    Но ведь... %define api.push-pull push


    1. rsashka Автор
      00.00.0000 00:00
      +2

      Вау, класс!
      Что-то я пропустил эту фишку у Бизона, из-за чего пришлось городить огород с подменой callback функций. Попробую заюзать этот функционал!


  1. agalakhov
    00.00.0000 00:00
    +2

    Могу порекомендовать пару Ragel + Lemon.


  1. Edward_Full-Stack
    00.00.0000 00:00

    Хочу услышать пост про новый язык программирования AsmX.


    1. vikitoriya
      00.00.0000 00:00
      +1

      Именно услышать или все же увидеть?


      1. Edward_Full-Stack
        00.00.0000 00:00

        Оба варианта подойдёт.


    1. slonopotamus
      00.00.0000 00:00

      Я тоже много чего хочу, и чо? Ну и кстати, гугл не считает что такой новый язык программирования вообще говоря существует.


      1. Edward_Full-Stack
        00.00.0000 00:00

        Почему тебе так важно узнать от Гугла, это же тьфу на тебя. Он существует, СУЩЕСТВУЕТ в просторах интернета. Он на гитхабе существует это репо:

        https://github.com/langprogramming-AsmX/AsmX