Liscript — web REPL: поцелуи, велосипеды и экскаваторы



    Некоторое время назад я написал интерпретатор лиспоподобного языка, который назвал Liscript. Опубликовал несколько статей на Хабре, посвященных особенностям реализации ядра, TCO, GUI, REPL-ботов и т.п. Недавно добавил web-интерфейс REPL-у (ссылка в конце статьи).

    При чем здесь поцелуи и экскаваторы? Думаю, большинству известны такие аббревиатуры, как KISS (keep it simple stupid — делай это проще, дурачок), YAGNI (You ain't gonna need it — Вам это не понадобится), а также высказывания людей разной степени великости про архитектурных астронавтов, «все должно быть сделано так просто, насколько возможно, но не проще», и т.п.

    Допустим, перед вами стоит задача — выкопать яму. Какие есть варианты решения? Взять лопату и выкопать самому — дешево и сердито, но долго и возможно неоптимально (зависит от вашего уровня владения лопатой и размеров ямы). Отдать на аутсорс таджикам (не будем рассматривать здесь этот вариант, хотя я должен был его упомянуть). Взять экскаватор — быстро и эффективно, но затратно: бензин/аренда, плюс не факт, что он проедет в вашу садовую калитку, значит надо сносить/восстанавливать забор и т.д. Также, необходимо определиться с моделью (порой из 100500 вариантов), а если вы будете управлять им самостоятельно, надо разобраться во всех его рычагах и педалях.

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

    В общем, про выбор инструментов под задачи, и конкретные (подозреваю, что спорные) решения, которые я выбирал в процессе реализации проекта, под катом.

    Реализация односвязного списка

    В Liscript отсутствуют точечные пары (cons-ячейки), так что основная структура как кода так и данных в нем — список. В Java-версии интерпретатора я написал собственный простейший класс с полями car и cdr. Можно было взять стандартные коллекции Java типа LinkedList или ArrayList — скорее всего они лучше оптимизированы в плане аллокаций и дружбы со сборщиком мусора. Но я выбрал свой собственный велосипед, хотя это несложно отрефакторить ко второму варианту.

    Токенизатор/Лексер/Парсер

    Это пример поинтереснее. Нужно читать входной текст/строку и преобразовывать ее в готовое AST (абстрактное синтаксическое дерево), выделяя из текста токены и организуя их в иерархическую структуру. Задача более чем известная, существует целая обширная теория синтаксического анализа и разбора, и множество библиотечных реализаций различных парсеров, например www.antlr.org Но я принял решение написать свой простой велосипед на конечном автомате — у Лиспа очень простая грамматика и синтаксис, хотя конечно пришлось учитывать многострочные строковые литералы и комментарии. Более того — таких велосипедов у меня 3: в Haskell-реализации (несмотря на множество готовых решений: parsec, attoparsec и т.д.), в ядре Java-реализации и в GUI на Swing для синтаксической подсветки текста кода.

    WEB backend


    История болезни: это мой первый web-проект, в качестве хостинга приложения я выбрал Heroku, бесплатный тарифный план. Из систем сборки в одном Java-чате мне посоветовали Gradle, а не Maven, мотивируя различными привлекательными словами. Я поверил, выбрал на Heroku вариант Gradle-сборки, и демопример, скачавшийся мне на диск, использовал Ratpack (не знаю что использует пример Maven-сборки, не исключено, что то же самое). Поскольку опыта с другими web-серверами (да и web-технологиями вообще) у меня не было, надо было делать выбор — или не использовать этот демопример, а установить какой-нибудь Jetty, по которому имхо гораздо больше доступной информации в интернете, или использовать Ratpack, готовый и настроенный, но как-то разбираться с ним. Выбрал второй вариант, среди найденных примеров две трети было на Groovy, но в конечном итоге удалось разобраться как реализовывать долгие синхронные операции, а с рендерингом страниц все было просто.

    WEB frontend


    Здесь вообще просто море вариантов выбора самых различных технологий, ибо область бурно развивается уже много лет. И апологеты разных фреймворков доказывают всему миру (и друг другу), что их выбор лучше/эффективнее/перспективнее и только так надо «писать WEB в 2017 году» (С) Каждый из них по-своему прав, но какой выбор был у меня? Для решения сугубо утилитарной задачи — вырыть яму написать web-моринтерфейс своего REPL-а.

    • Использовать Java-фреймворки, умеющие генерировать вэб-интерфейс: Vaadin, FX и т.п. Тяжелые мощные экскаваторы, поддерживающие парадигму «написано однажды — копает везде».
    • Использовать многочисленные WEB-фреймворки/библиотеки, имя им легион: Angular, React,… Каждый со своим богатым внутренним миром блек-джеком функционалом и свистелками.
    • Написать все самому на голом ванильном Javascript / HTML / CSS.

    Как вы наверное догадываетесь, я конечно же выбрал третий вариант :). Даже без использования jQuerry, потому что этот станок пришлось бы тоже изучать. Без Babel / Webpack и еще 100500 модных на фронте слов. Впервые в жизни ощутив радость отладки под различные браузеры, включая IE, я был вынужден захардкодить в CSS константы, вместо объявления их в начале файла, что само по себе убого, но IE у меня не хотел видеть эти объявления, а технологии генерирования CSS из других форматов, как предлагают различные модные фреймворки, подразумевали использование упомянутых фреймворков.

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

    Сплит-панели

    Мне для REPL-а они были очень нужны. Мощные фреймворки для вэб-GUI реализуют их в своем составе, но, исключив тяжелую технику, я рассмотрел более мелкие готовые реализации, и, попробовав 3-4, таки выбрал одну. Хотя она тянет за собой jQuerry, что мне не очень нравится (больше мне пока ни для чего он не нужен), но гораздо лучше реагирует в плане отзывчивости интерфейса. Если будет время/желание, может перепишу на свой велосипед, без jQuerry.

    Подсветка кода

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

    Заключение


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

    Главная страница сайта Liscript
    Онлайн REPL
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 24

      +3

      Я прочитал какой-то текст.

        +2

        Не знаете что говорят по поводу продакшн-версии WebAssembly?

          +2

          Знаю. Из первых рук: урожай бузины здеся не то что урожай дядек тама.

        0
        Что касается таджиков и экскаваторов. Цена обычно равнозначна, а второй вариант качественнее и быстрее. То же касается и других работ. Знаю, т.к. столкнулся лично.

        Наверное это весь комментарий, который я хотел бы написать к этой статье.
          0
          Я бы не стал брать экскаватор чтобы сделать крыльцо. (пример не очень, имеется ввиду объем)
            0

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

              0
              Есть такие маленькие, прикольные экскаваторики. Вполне себе выкопают что-нибудь небольшое.

              Но, согласен, выбирать нужно учитывая окружение, удобство и здравый смысл.
            0
            Как вы наверное догадываетесь, я конечно же выбрал третий вариант :). Даже без использования jQuerry, потому что этот станок пришлось бы тоже изучать.

            Но при этом сразу же его используете, чтобы использовать плагин SplitPane :)


            И касательно CSS я могу вас ещё разочаровать, что комментарии // он на самом деле не умеет.

              0

              Я и написал дальше в статье, что jQuerry вынужден тащить только из-за SplitPane, как багаж.


              Про комментарии спасибо, не знал. Разочарования нет, просто учту на будущее :)

              0
              github.com/nathancahill/Split.js
              Смотрели сюда?
                0

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

                +1

                Для примера надо было еще заменить все маленькие i на скриншоте на большие. И получился бы отличный пример как не стоит именовать переменные

                  +1

                  Ну зачем же писать ещё один интерпретатор брейнфака? :-) Я вот пытаюсь взять идеи лиспа и сделать язык с человеческим лицом. Моя идея в том, чтобы избавиться от бесконечных скобочек и хаотичного форматирования в пользу формата, заточенного для AST: tree — дерево типизированных узлов и ничего более.


                  Для примера приведу один из тестов, в котором указан эквивалентный код на лиспе:


                  test
                      name \get last element of list
                      lisp \( car ( last '( one two three ) ) )
                      case tail
                          one
                          two
                          three
                      case three

                  Тут мы создаём список из 3 элементов и получаем из него последний. Не смотря на чуть большую "объёмность", язык jack значительно проще для восприятия при не меньшей мощности.


                  Я не большой спец в лиспе. Он меня пугает, как и 90% разработчиков. Думаю изменение синтаксиса исправит эту ситуацию. Буду рад дельным советам.

                    –1

                    Каждый кулик свои предпочтения защищает. Мне, например, скобочки ничуть не мешают и выглядят очень по-человечески. Впрочем, вы можете выйти с вашим предложением к тому же Ричу Хикки, напишете ему про то, зачем он писал "ещё один интерпретатор брейнфака" и как изменение синтаксиса и убирание скобочек увеличит его комьюнити в 9 раз (придут 90% бывших напуганных разработчиков).

                      0

                      UPD: простите, ошибся — конечно же в 10 раз

                        0
                        Лиспов без скобочек много (например, dylan, ну а если немного упороться, то можно посчитать python за лисп — norvig.com/python-lisp.html), и все они хуже лиспов со скобочками. Скобочки вообще неважны — в джаваскрипте их ГОРАЗДО больше, чем в лиспе (если брать common lisp), и ничего, люди живут как-то.

                        Нормального разработчика нормальные языки не пугают — ни лисп, ни хаскель, ни R. Common lisp крайне практичный инженерный язык, нет смысла делать что-то другое «на идеях лиспа», ибо получится явно хуже. Если хочется более понятного и «чистого» (в смысле научности) языка — то есть scheme, тоже очень достойная штука, особенно если это racket, но в плане библиотек и общей взрослости CL всё-таки на голову выше.

                        И да, синтаксис — это не идея лиспа, это следствие. Если синтаксис поменять, то лисп как раз потеряет одну из своих основных идей — регулярность/консистентность.
                          0

                          dylan, python — я не увидел в них ничего похожего на лисп. Обычные ООП языки с куцым метапрограммированием в рантайме.


                          Common lisp крайне практичный инженерный язык, нет смысла делать что-то другое «на идеях лиспа», ибо получится явно хуже.

                          Необоснованное утверждение. Вы считаете лисп верхом совершенства?


                          Если синтаксис поменять, то лисп как раз потеряет одну из своих основных идей — регулярность/консистентность.

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

                            –1
                            Вы считаете лисп верхом совершенства?

                            А разве в этом кто-то сомневается (из тех, кто знает CL не понаслышке)?

                              0

                              Не дружелюбен к новичкам — уже не идеал.

                                0

                                Ваш синтаксис тоже недружелюбен к новичкам.

                                  +1

                                  Обоснуйте.

                                    0

                                    Для начала, он вообще не похож ни на что, привычное новичку. Он не похож ни на HTML/XML, ни на Си, Паскаль, Руби, Шарп, Джаву, Лисп… Разве что на Пайтон и Ямл похож вложением отступами.


                                    Далее, вы сами пишете: "Видя рядом расположенные куски исходного файла и сгенерированный код, вы быстро начинаете понимать этот птичий синтаксис". То есть предлагается самостоятельно исследовать правила синтаксиса и семантики вашего языка на основе примера. Сомнительно, что это под силу большинству новичков.


                                    Тем не менее, у вашего языка есть документация. Находится, она правда, весьма не сразу. Для начала, на официальном сайте фреймворка есть только примеры. И даже они не разбираются, а просто даются исходники в том самом "птичьем" синтаксисе. Новичку предлагается пройти на Гитхаб, где в Readme дойти до места, где появляются примеры шаблонов, пройти по ссылке на компонент (!) $mol_view, а там, в свою очередь, дойти до раздела с непосредственным описанием языка шаблонов.


                                    Наконец, язык шаблонов в полном противоречии правилам дидактики и эргономики грубо нарушает правило наименьшего удивления. Буду ориентироваться на ваши примеры:


                                    $my_button $mol_view


                                    Новичок здесь видит пару идентификаторов, означающую наследование. В си-подобном синтаксисе такая пара скорее всего означала бы, что $mol_view имеет тип $my_button (хотя тут имеется в виду скорее обратное), либо (как в Ямле), что переменная $my_button имеет значение $mol_view (это чуть теплее, но в очень грубом приближении). Вдобавок, позже окажется, что знак доллара здесь обязателен и указывает на то, что это именно имя класса (то есть компонента).


                                    Далее с типичным пайтоновским вложением описываются методы описываемого класса в виде пары метод-реализация. При этом для возврата строковой константы требуется бэкслеш. В вашем описании он подразумевает собой "экранирование", но не последующего символа (как в Си), а всей строки. И это в то время, когда практически любой программист сразу же воспринял бы значение как строковую константу будь она заключена в кавычки.


                                    Затем вы показываете возврат массива. Здесь за именем метода следует обычный слэш, а далее просто список его значений без какого-либо дополнительного синтаксиса. При этом с дефиса ВНЕЗАПНО объявляются комментарии, что неожиданно для знающих синтаксис Ямл или Маркдаун.


                                    Позже оказывается, что можно вернуть и джаваскриптовый объект (ассоциативный массив), при этом вместо слэша надо указать звёздочку, и тогда дочерние пары идентификаторов ВНЕЗАПНО становятся ключом и значением такого массива. То есть ваш синтаксис контекстно зависим.


                                    Далее вы вводите операторы байндинга, и у новичка случается разрыв шаблона. Во-первых, они способны сами порождать методы создаваемого класса, притом, мемоизированные. Во-вторых, фоллбэк к дефолтному значению передаётся пробелом, передача параметра через ?, а указание ключа через !. Вряд ли какой-либо общеизвестный синтаксис применяет такие неочевидные обозначения для часто используемых операций. В самом деле, в каком страшном сне новичок воспримет запись <= name?val \ как вызов метода name (неявно будет создан, если его явно не объявили), в который или можно пробросить аргумент, или он вернёт свой результат, или вернёт пустую строку?


                                    Вот и получается, что Лисп, где практически всё определяется структурой (функция значение1 значение2 значение3...) намного очевиднее, а потому дружелюбнее, новичкам.

                                      0
                                      Для начала, он вообще не похож ни на что, привычное новичку.

                                      Новичку вообще никакой язык не привычен.


                                      Разве что на Пайтон и Ямл похож вложением отступами.

                                      Python, YAML, HAML, Jade, CoffeeScript… Их много и с каждым годом появляется всё больше. Как вы думаете, почему?


                                      Далее вы почему-то пишете про язык view.tree, а не про формат tree и уж тем более не про язык jack.tree. Но ладно, давайте разберём и его, раз он вызывает у вас столько боли...


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

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


                                      Для начала, на официальном сайте фреймворка есть только примеры.

                                      Может потому, что это не "оффициальный сайт", а демонстрационное приложение? "Оффициальный сайт" — страница на гитхабе.


                                      в Readme дойти до места, где появляются примеры шаблонов, пройти по ссылке на компонент (!) $mol_view, а там, в свою очередь, дойти до раздела

                                      Как вы считаете, может быть выделить эту ссылку капсом?



                                      переменная $my_button имеет значение $mol_view (это чуть теплее, но в очень грубом приближении)

                                      Да нет, это действительно так: $my_button возвращает $mol_view с переопределёнными свойствами. Следующие два кода семантически эквивалентны:


                                      $my_button $mol_view
                                          title \hello

                                      Button $mol_view
                                          title \hello

                                      Разница лишь в том, что первый код объявляет класс, а второй — локальную фабрику.


                                      Вдобавок, позже окажется, что знак доллара здесь обязателен и указывает на то, что это именно имя класса (то есть компонента).

                                      Все глобальные имена в $mol начинаются с доллара. Не важно, класс ли это, или функция, или поле, добавляемое в объект примесью.


                                      Далее с типичным пайтоновским вложением описываются методы описываемого класса в виде пары метод-реализация.

                                      Описываются свойства со значениями по умолчанию. Но компилируется это в полиморфные методы, да.


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

                                      Как вы думаете, почему в YAML есть 6 форм записи строк, только 2 из которых — в кавычках? Почему в PHP есть 5 форм записи строк, только 2 из которых — в кавычках? И почему в tree достаточно лишь одной формы, и та — без кавычек?


                                      Здесь за именем метода следует обычный слэш, а далее просто список его значений без какого-либо дополнительного синтаксиса.

                                      А зачем тут ещё какой-либо дополнительный синтаксис? Что может быть естественней, чем каждый элемент с новой строки?


                                      При этом с дефиса ВНЕЗАПНО объявляются комментарии, что неожиданно для знающих синтаксис Ямл или Маркдаун.

                                      И ВНЕЗАПНО совершенно обыденная вещь для знающих синтаксис SQL, HTML, XML, SGML.


                                      указать звёздочку, и тогда дочерние пары идентификаторов ВНЕЗАПНО становятся ключом и значением такого массива. То есть ваш синтаксис контекстно зависим.

                                      Любой язык со значимыми отступами контекстно зависим. Я даже скажу более — любой сколь-нибудь полезный язык контекстно зависим.


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

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


                                      Во-вторых, фоллбэк к дефолтному значению передаётся пробелом

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


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

                                      Предложите синтаксис по лучше.


                                      Вот и получается, что Лисп, где практически всё определяется структурой (функция значение1 значение2 значение3...) намного очевиднее, а потому дружелюбнее, новичкам.

                                      Если вам лень ходить по ссылкам, то замечу, что в jack.tree структура столь же тривиальная:


                                      функция
                                          значение1
                                          значение2
                                          значение3
                        –1

                        vintage аплодирую стоя, но мне синтаксис JSON ближе.

                        Only users with full accounts can post comments. Log in, please.