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

в 17:08, , рубрики: python, Компиляторы, языки разметки

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

image


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

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

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

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

Идея

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

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

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

Синтаксис

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

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

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

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

Реализация

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Результаты

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

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

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

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

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

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

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

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

Автор: san-smith

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js