Как я создавал убийцу HTML

В жизни каждого начинающего программиста-самоучки есть момент, когда смотреть очередное видео из серии «Основы %language_name%» уже не интересно, а проситься в джуниоры ещё рано. В этот момент хочется опробовать свои силы на каком-нибудь «почти настоящем» проекте. Под катом — история о том, как я придумал такой проект и что из этого вышло.

image

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

После знакомства с некоторыми языками программирования и проектами уровня «Hello world!» на них, мне попался на глаза Python.

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

Однажды, размышляя над идеей такого проекта, я увидел картинку из начала поста и меня осенило: нужно запилить свой язык разметки, с русскоязычными командами и скобками!

Идея


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

  • Все команды должны быть доступны в стандартной русской раскладке;
  • Компиляция в HTML;
  • «Нет!» закрывающимся тегам.

Первое требование было продиктовано самой концепцией, второе — желанием увидеть результаты работы на практике. Что касается третьего требования — ещё со школы не любил писать дублирующие закрывающие теги (вроде </body> и </bold>), хотя их необходимость неоспорима. Поэтому было решено ввести универсальный «закрыватель», который закрывал бы любые теги.

Синтаксис


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

В общем виде команды языка ЯГТР можно описать следующим образом:

\команда::атрибуты::(текст)

Часть с атрибутами является необязательной. Использование :: для отделения атрибутов может показаться необоснованным решением (например [] или {} были бы более наглядными), но это жертва, принесенная в дар «русскоязычности» раскладки.

Реализация


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

И вот тут мне пригодились знания по теории автоматов, полученные в рамках университетского курса «Теоретические основы информатики». Помнится, тогда они мне казались бесполезными — быть может, дело в том, как этот курс читался.

Алгоритм работы компилятора разбивался на 2 этапа:

  • Получение из исходного текста списка токенов;
  • Использование полученного списка для генерации кода.

Для получения списка токенов исходный текст просматривался практически посимвольно, если встречался «граничный» символ, то текущий токен добавлялся в список. Границами токенов могли служить: \, (, ), %, команды, символ конца строки и др.

На втором этапе компиляции использовался конечный автомат, который хранил текущее состояние и, возможно, стек состояний. Рассмотрим на примере.

Код должен был выглядеть примерно следующим образом:

\пар(Этот текст будет \ж(полужирным), а этот - \к(курсивом))


Что после компиляции должно было превратиться в

<p>Этот текст будет <b>полужирным</b>, а этот - <i>курсивом</i></p>

Для реализации автомата были созданы:

  • TokenList — список, хранящий токены;
  • STATE — текущее состояние автомата;
  • statement — стек состояний

Изобразить это можно следующей схемой:


Пусть на вход автомату первым подается токен \пар. Так как этот токен является командой языка, то он задает новое состояние (<p), которое и становится текущим, а предыдущее состояние (<body>) сохраняется в стек состояний.


Обратите внимание: новым состоянием становится именно <p, а не <p> — это связано с тем, что названия состояний используются для генерации кода, а перед символом > могут быть некоторые атрибуты тега. Фактически, есть два различных состояния: <p и <p>, в последнее автомат переходит, когда на вход ему подается токен "(". Подобная схема применялась для всех команд, поэтому было принято считать первые состояния «неполными тегами», а вторые — «полными тегами».

Если на вход поступает токен ")", а текущее состояние является «полным тегом», то в генерируемый файл добавляется парный тег (например тег </p>), а текущим состоянием становится снятое с вершины стека последнее состояние.
Разбор заканчивается, когда после очередной ")" оказалось, что стек состояния пуст.

Текст между :: и :: считается атрибутами тегов. Предполагалось, что для их парсинга будет использоваться свой алгоритм разбора, поэтому сначала он просто добавлялся между именем тега и символом >. С одной стороны это позволило использовать внутри команд ЯГТР синтаксис HTML, с другой — этот текст никак не анализировался, поэтому не было гарантий, что в нем нет ошибок с точки зрения HTML.

Результаты


В целом результаты работы можно оценить как неоднозначные.

Изначально целью проекта было прокачать свои навыки в программировании на python и создать продукт, который было бы не стыдно показать потенциальному работодателю (ну и потешить ЧСВ, конечно). И если с первой частью более менее хорошо (лучше освоил язык, плюс немного разобрался с github'ом), то со второй не так всё гладко.

Некоторые проблемы, которые так и не были решены:

  1. Проблемы с реализацией аналога тега <pre>;
  2. Анализ атрибутов не реализован — фактически, они просто подставляются в тег HTML;
  3. Идея отказа от переключения раскладки оказалась (пока?) несостоятельной — атрибуты и стили всё равно приходится писать на английском, реализации какого-нибудь ЯКТС нет даже в проекте;
  4. Неоднозначное решение: компилятор на интерпретируемом языке;
  5. Реализованы не все теги;
  6. Написать «настоящий сайт» на ЯГТР можно, но знать HTML всё равно необходимо.

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

С другой стороны, появились и некоторые новые мысли. В частности, хотелось бы разобраться как правильно организовать опции компилятора через аргументы командной строки. Или как создать текстовый редактор с подсветкой синтаксиса ЯГТР. Но тут я даже не знаю с какого края ухватиться.

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

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

Комментарии 70

    0
    Не <a>, а <я>! Anchor = якорь.
      +23
      Да сколько ж можно! <а> — это абзац, <p> по-буржуйски.
        0
        мопед картинка не моя, я только в пост разместил.

        А если серьезно — вопрос о том, как назвать очередную команду оказался нетривиальным. Хотелось, чтобы команды были понятными, но краткими, а иногда просто было сложно придумать русскоязычный аналог.
        Например, как адекватно перевести на русский <a> и <link>? Ведь и то, и другое — ссылка.
          0
          — это ссылка, а — это anchor (якорь). Он может быть использован не только для перехода в другое место, но и как маркер на странице, к которому можно перейти.
            –1
            Нет, я про другое: <a> — это тег HTML, который используется для создания ссылок на файлы (например, другие страницы сайта), а <link> — тег, который используется для подключения, например, файлов со стилями.
            Но ведь фактически, и то, и другое — ссылки. Как адекватно перевести названия этих команд на русский?
              0
              Похоже понял, что вы имели в виду (видимо теги «съело»).
              Возможно вы правы и такой вариант именования команд был бы более верным. Хотя я всегда думал, что <a> — это ссылка (зато теперь понятно, почему «а» — спасибо за ликбез).
              0
              Нормально краткими вы их не сделаете никак просто из-за структуры языка. Средняя длинна слова в англ. яз. 4 символа, поэтому многие сокращения это почти само слово. В рус. яз. же средняя длина слова 7 символов.

              Вот если бы вы изобрели трехбуквенный тег, которым можно было бы описывать страницы не хуже, чем командуют в армии...!!!)
          +13
          Вы переизобрели синтаксис LaTeX.
            +2
            Вообще говоря да, я сознательно использовал похожие на латех команды. Но можно ли говорить, что это синтаксис LaTeX?
              0
              Но можно ли говорить, что это синтаксис LaTeX?

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

              Вы сами отвечаете на свой вопрос. Это не синтаксис LaTeX, это похожие на LaTeX команды.
            0
            Или как создать текстовый редактор с подсветкой синтаксиса ЯГТР. Но тут я даже не знаю с какого края ухватиться.

            Для начала можно попробовать посмотреть на генераторы парсеров по грамматикам — например, на pegjs. Далее можно попробовать свои силы в написании плагина к какому-нибудь редактору. В частности, например, для NetBeans-а еще нет подсветки pegjs, а сделать ее не очень сложно. Самое интересное, что на основе примера интеграции новой подсветки языка можно реализовать подсветку грамматики pegjs с помощью самого pegjs (если сначала портировать его на java. Впрочем, можно воспользоваться и другим парсер-комбинатором, но это не так эпично)! Я и сам хочу этим заняться, но пока поглощен другим проектом.

              +1
              Можно взять готовый компонент Scintilla — это такой крутой встраиваемый редактор с подсветкой синтаксиса, например Notepad++ или FlashDevelop поверх него сделаны. Очень мощная и в то же время довольно простая штука для создания редакторов для каких-то своих форматов исходного кода.
                0

                Да, Scintilla прекрасный компонент, больше всего он мне нравится тем, что сворачивает код железно везде, а не игнорирует какие-то странные случаи, как, к сожалению делает NetBeans для Java-классов на верхнем уровне файла (кто им сказал, что их не нужно сворачивать? В одном файле, вообще-то, несколько package-private классов может быть и невозможность их свернуть очень огорчает) или MSVS (почему-то иногда не считает нужным сворачивать блоки в фигурных скобках внутри функций. Километровые if-ы и switch-и читать просто "удовольствие"). Плюс нормальные маркеры свертывания, а не как в каждой IDE изголяются кто во что горазд — то линию "от сих до сих" прячут, пока не наведешь мышь (кому она мешает?), то вместо нормальных ± какие-то стрелки понатыкают. SciTE до сих пор мой любимый редактор именно из-за этой простоты и детерминированности, всяким Sublime-ам и Atom-ам до него еще расти и расти в этом плане.


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

            • НЛО прилетело и опубликовало эту надпись здесь
                +7
                Статью не читай @ комментарий пиши.
                  –3
                  зануда_mode
                  Вы не правильно используете знак «коммерческое at», он по смыслу совсем для других целей.
                  Например:
                  мыло @ сервер
                  5А @ 220v
                  130W @ 25℃
                  зануда_mode_off
                  P.S. как и многие другие. Что за мода пошла?
                    +7
                    В данном случае символ это означает собаку и используется для того, чтобы упомянуть известное графическое макро с собакой, раздающей плохие советы.
                      +2
                      Спасибо, не знал про это. Но всё равно не могу воспринимать символ собаки как-то по-другому. Я бы лучше слэш использовал.
                        0
                        Не можете или не хотите?
                        Слеш в Интернете уже используется — в значении, близком к «или».
                          0
                          Вот чёрт! Тогда вспомним bash и используем два амперсанда, так логика будет соблюдена. Да пусть даже один.
                            0
                            Не совсем. Чтобы логика была соблюдена, нужна скорее точка с запятой. «И» подразумевает одновременное выполнение условий.
                              0
                              Так здесь и подразумевается одновременное выполнение условий.
                  • НЛО прилетело и опубликовало эту надпись здесь
                      0
                      Картинка к посту придумана не мной. Собственно, изначальной идеей и было, чтобы в языке не нужно было переключать раскладку. Поэтому {} и [] в языке не используются.
                    0
                    Это смотря какая раскладка.
                      0
                      Да, все равно надо будет переключаться.
                        +1
                        В ЯГТР не используются [] и {}, так что переключаться не обязательно. Другое дело, что если нужны будут атрибуты (например для \рис) и стили, то тут да — фактически придется писать код на HTML.
                      +1
                      Вот это поворот. Теперь я знаю что обратный слэш и в русской раскладке можно набрать. Правда зачем…
                        0
                        Ничего особо нового. В студенческие времена часто писал на vb6, используя всё на русском — и переменные, и функции. Смотрелось прикольно, но только и всего. Никакой разницы, на каком языке называть функции, переменные, и т.п. — только неудобства для зарубежных разработчиков (всё же английский знают больше народа, нежели русский, хоть сколько бы я ни был патриотом). ИМХО, для программирования лучше просто не использовать вовсе какой-либо второй язык — только английский. Потому что надо отделять код от данных, а данные уже забивать можно на каком угодно языке. Правда, для небольших проектов это не оправданно, но там и потери на переключение раскладки будут небольшими — не сравнить с изобретением нового html.
                          0
                          ИМХО, для программирования лучше просто не использовать вовсе какой-либо второй язык — только английский.

                          только неудобства для зарубежных разработчиков

                          Не спасет именование функций латиницей, код подразумевает задачи, комментарии, документацию, обратную связь. Если нет поддержки по всем фронтам, то это все равно не интернациональный проект, а значит, аргумент отклоняется.
                            +2
                            На самом деле язык создавался just for fun.
                            Важнее было прокачать навыки программирования, а тут просто попалась на глаза интересная идея.
                              0
                              Надо было в теги добавить «ненормальное программирование».
                                0
                                Сделано.
                            +2
                            Вроде бы с аргументами командной строки в питоне все просто — модуль, кажется, argparse. В документации хорошие и понятные примеры.
                              0

                              С появлением docopt, остальное кажется монструозным.

                                0
                                Есть минус: не включено в стандартный комплект.
                                  0

                                  Docopt однофайловый, можно просто скопировать к себе в проект.

                              0
                              А как насчет экранирования, например когда код на подобии такого:
                              \пар(Этот текст ( будет \ж(полужирным), а этот - \к(курсивом))
                              \пар(Этот текст \пар( будет \ж(полужирным), а этот - \к(курсивом))


                              Для создания редактора с подсветкой синтаксиса ЯГТР, можно использовать библиотеку wx там есть достаточно мощный редактор кода Scintilla:


                              А это пример кода с аналогичным функционалом на lua:
                              utf8=require'lua-utf8'
                              
                              s=[[\пар(Этот текст будет \ж::class='bold'::(полужирным), а этот - \к(курсивом)\изо`align='center'`)]]
                              
                              
                              --хеш для тегов с телом
                              local complex_tags = {
                              	['пар']='p', ['ж']='b', ['к']='i'
                              }
                              
                              --хеш для тегов без тела
                              local simple_tags = {
                              	['бр']='br', ['изо']='img'
                              }
                              
                              --паттерны для разбора тегов, без начального символа "\" 
                              local complex_tag_pattern = [[:?:?([^:%)%(\]*):?:?(%b())]]
                              local simple_tag_pattern = [[:?:?([^:%)%(\]*):?:?]]
                              
                              --рекурсивная функция в которой все и происходит 
                              local function pp(str)
                              	--обрабатываем теги без тела
                              	for ya_tag, html_tag in pairs(simple_tags) do
                              		str=utf8.gsub(str, [[\]]..ya_tag..simple_tag_pattern,
                              			function(attrs)
                              				local s = '<'..html_tag
                              				if #attrs>0 then s=s..' '..attrs end
                              				s=s..'>'
                              				return s
                              		end)
                              	end
                              
                              	--тоже но уже для тегов с телом
                              	for ya_tag, html_tag in pairs(complex_tags) do
                              		str=utf8.gsub(str, [[\]]..ya_tag..complex_tag_pattern,
                              			function(attrs, body)
                              				local s = '<'..html_tag
                              				if #attrs>0 then s=s..' '..attrs end
                              				s=s..'>'..pp(utf8.sub(body, 2, -2))..'</'..html_tag..'>'
                              				return s
                              		end)
                              	end
                              
                              	return str
                              end
                              
                              --выводим результат
                              print(pp(s))
                              

                                0
                                А как насчет экранирования, например когда код на подобии такого:
                                \пар(Этот текст ( будет \ж(полужирным), а этот — \к(курсивом))

                                Для экранирования используется \ — так, чтобы ввести символ "(" нужно писать \(

                                \пар(Этот текст \пар( будет \ж(полужирным), а этот — \к(курсивом))

                                А в данном случае компилятор выдаст ошибку — для внешнего \пар не будет найдена закрывающая ")".

                                По поводу примера: с lua не знаком, ничего сказать не могу. Но могу сказать, что сначала тоже думал использовать регулярные выражения, но не осилил.
                                0
                                случайно, не знакомый автора этой статьи?
                                  0
                                  Нет, эта статья раньше мне не попадалась.
                                    +2
                                    осталось объединить эти две статьи в «если бы React и JSX сделали в СССР»
                                  0
                                  А почему не на Brainfuck?
                                    0
                                    Просто потому что хотел научиться программировать на Python.
                                    0
                                    Ты слуцайно не фанат 1C?
                                      +1
                                      Нет, не фанат.
                                      +2
                                      Хе-хе ;-)

                                      image

                                      _ГОСТ 27974-88_

                                      А если серьезно, то Алгол-68 можно назвать _«предтечей html»_.
                                        +3
                                        Всё-таки Алгол — язык программирования, а HTML — язык разметки.
                                          0
                                          Бесспорно так. Это разные сущности конечно.

                                          В то время машинных языков программирования было раз два и обчелся. И А-68 был очень передовым на момент своего рождения. Настолько, что компилятор писали к нему аж десять лет. И потом ещё дописывали до конца восьмидесятых годов, чтобы наконец реализовать полностью все возможности которые в него были заложены. Так что он мощнейший толчок дал и стимулы всей индустрии тех лет. И много всяких идей породил.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                          +9
                                          поздравляю, вы что-то написали в комментарии.
                                          0
                                          На самом деле меня уже давно мучает вопрос замены тега . Ведь сейчас визуализация страницы занимает гораздо большее время, чем её скачивание. Всё из-за рекурсивных пересчётов позиций элементов и блоков.
                                          Можно, например, взять другую систему выравнивания. К примеру Android. Но только меня беспокоит, что требования заказчиков превратят лаконичную нерекурсивную разметку обратно в Html.
                                            0
                                            замены тега
                                            <html>
                                              0
                                              Извините, а в каком смысле употребляется в данном контексте слово рекурсивная? И как оно применяется к парным тегам, просто немного не уловил мысль. Спасибо!
                                                0
                                                В том смысле, что сиблинги блока и внутренний контент влияют на него самого
                                                0
                                                Ну давайте не забывать, что во время «отрисовки» страницы всё ещё докачиваются картинки, посторонний мусор и выполняются скрипты. Когда у тебя 40 метров картинок и 5 метров скриптов (и лишь за 1 из них стоит поблагодарить рекламу) для отображения сайт-визитки, то как-то не удивительно, что отрисовка этой всей бодяги занимает больше, чем скачивание.
                                                0

                                                Насчет pre можно использовать подход LaTeX, ну или похожий на него


                                                \начало пфт
                                                Текст Предварительно Форматированного Текста
                                                Следующая строка
                                                \конец пфт
                                                  0
                                                  Тогда выбивается из системы: команда в общем случае выглядит \команда::аргументы::(текст), а подход латеха сильно отличается.
                                                    0

                                                    Ну если стандарт позволит то можно попробовать так:


                                                    \пфт(
                                                    Многострочный текст,
                                                    здесь
                                                    
                                                    monospace
                                                    )
                                                      0
                                                      На самом деле, проблема немного в другом зарыта. Так как символ ")" используется в качестве «закрывателя» команд, то для его вывода на экран используется последовательность \).
                                                      Вопрос следующий: если я захочу вставить символ ")" внутри команды \пфт, мне ставить перед ним "\"? Если да, то какой же это преформатированный текст? А если нет, то как определить конец команды?
                                                        0

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

                                                          0
                                                          На самом деле, такая команда фактически уже есть: ведь текст между :: и :: пока вставляется в документ как есть. Можно придумать аналог, но это выбивается из общей картины. Кстати, строго говоря, вариант
                                                          \начало пфт
                                                          Текст Предварительно Форматированного Текста
                                                          Следующая строка
                                                          \конец пфт
                                                          — это почти то же самое, что и пара ::, только здесь начало и конец отличаются, а случае с ::, они являются признаком и начала, и конца.
                                                  +1
                                                  Проиграл с цвет «военноморсокгофлота», спасибо.)
                                                    +1
                                                    Теперь одинэсники тоже смогут верстать? :-)
                                                      0
                                                      Python — хороший язык. Но и идею русификации тоже далеко откладывать не надо, потому как она может стать снова актуальной. Учитывая курс на импортозамещение, русификация ЯП становится вполне логичной. Да и традиции имеются.

                                                      image
                                                        +2
                                                        Помню, захотелось мне в далеком 1998 году тоже свой язык для своего фидошного проекта. Т.к. я ничего не знал, книжек не было, учителей тоже, а проект был изначально вообще под DOS, то я просто случайно нашел в Borland Pascal валидаторы (по сути regexp-ы) и с их помощью вполне успешно написал свой.овно-язычок.
                                                        Он даже в байт-код (я такого слова еще не знал) компилировался, чтобы не разбирать его при каждом запуске.
                                                        Даже скриптов понаписывал на нем. Эх, если бы я тогда учился где-то у нормальных преподов, может и вышел бы из меня толк.

                                                        Целые трактаты писал, было время и желание, сейчас так делать уже лень :-)
                                                        https://github.com/hiddenman/pointmaster/blob/master/trunk/utf8/script/example.pms
                                                          0
                                                          Автор «адаптировал» к русской раскладке формат RTF =)
                                                            0
                                                            Сейчас посмотрел пример на Википедии:
                                                            {\rtf1
                                                            Привет!
                                                            \par
                                                            {\i Это} некий
                                                            отформатированный {\b текст}.\par
                                                            }

                                                            Даже не знаю, как по мне — общего только то, что команда начинается с символа \. А вообще, выше правильно заметили, что синтаксис больше похож на латех.
                                                            –2
                                                            Я бы тебе руки оторвал за эту ересь, да еще и бесполезную…
                                                              0
                                                              Как гласит народная мудрость, «Не любо — не слушай, а врать не мешай».
                                                              Тем более, что лично для меня это было полезно.

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

                                                            Самое читаемое