Пишем свой собственный компилятор или заново изобретаем SASS

Vladislav Kopylov
4 min readFeb 10, 2018

--

Photo by Artem Sapegin on Unsplash

Профессия разработчика прекрасна не только тем что ей можно обучиться самому проходя онлайн-курсы или читая книги. Но и получить небольшой опыт самостоятельно: написать свой аналог (или доработку) существующей библиотеки или продукта “just for fun” и залит его на github.

Лично я убежден, что каждый разработчик должен потратить личное время чтобы написать свою CMS, интерпретатор/компилятор или свою js-библиотеку для создания интерактивных интерфейсов 😎. Это очень хорошая самостоятельная практика языка и доведения проекта до конца.

Свою CMS я писал дважды, так что пришло время написать компилятор.

Зачем нужно писать компилятор/интерпретатор?

Каждый из этих инструментов решает свою специфичную задачу.

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

Более конкретный пример: Современные браузеры понимают только язык javascript, но это не повод писать на нем клиентскую часть. Есть много языков которые компилируются в него.

Интерпретатор выполняет задачу обработки и мгновенного выполнения исходной программы или запроса, без компиляции его в другой язык. Есть много интерпретируемых языков программирования, таких как Ruby, Python, Javascript и др.

Более конкретный, рабочий пример: Отдел маркетинга каждый день посылает запрос IT-отделу на выгрузку данных из БД в csv-файлы. При этом форматы метрик, по которым надо сделать выгрузку, могут часто меняться. Чтобы не продолжать нагружать программистов каждодневным переписыванием кода, можно реализовать интерпретатор, который будет представлять собой ограниченную версию SQL и будет более понятен типичному сотруднику. Благодаря этому отдел маркетинга получает возможность делать выгрузки самостоятельно и быстро.

Пишем свой компилятор

Перед тем как начать писать, нужно было определиться с функционалом.

При выборе функционала компилятора стояли следующие требования:

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

Идеи о создании компилятора языка программирования отошли на второй план. Был выбор между написанием аналога препроцессинга SASS или шаблонизатора HAML/SLIM. В итоге был выбран SASS.

Осталось определиться с функционалом. Я поставил цель реализовать следующие фичи:

  • Использование многострочных комментариев
  • Использование переменных
  • Использование миксинов
  • Вложенность селекторов

Но и появился ряд ограничений:

  • Переменные и миксины не могут быть вложенные
  • Миксины не имеют входящих аргументов

Структура приложения

Само приложение представляет собой механизм компиляции (machine) и CLI (консольный интерфейс).

Консольный интерфейс — это стандартный bin-файл. А механизм компиляции состоит из трех частей:

  • Парсер — здесь логика преобразования текста в абстрактное синтаксическое дерево (AST).
  • Трансформатор — здесь логика преобразования вложенного синтаксического дерева в не вложенную структуру.
  • Компилятор — здесь логика компиляции (отрисовки) синтаксического дерева в файл.

Парсер

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

Иными словами: нужен инструмент который способен преобразовать сплошной текст в набор коротких, последовательный и вложенных процедур. Относительно которых можно легко описать правила взаимодействия машины с ними. Парсер выполняет задачу: “преобразуй этот текст в набор инструкций которыми будет удобно манипулировать машине”.

Более подробно про его работу:

И так, у нас есть файлик с css, который надо преобразовать.

Парсер проходит по тексту, удаляет комментарии, разбивает текст на минимальные логические части. В данном случае, для разделения текста на минимальные логические части, достаточно разбить текст относительно символов ‘;’, ‘{‘, ‘}’. В итоге, текст преобразовывается в массив строк.

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

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

Вот пример работы для более сложной структуры.

До (scss файл):

После (AST):

Трансформатор

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

Иными словами: Трансформатор выполняет задачи:

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

Файл с таким содержимым:

После прохождения парсера и трансформатора предоставит следующую структуру:

Более сложный пример.

До:

После:

Компилятор

Это часть системы, которая проходит по абстрактному синтаксическому дереву и на основе его структуры отрисовывает css-файл.

Обход дерева проходит в два этапа:

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

  • Значения переменных сохраняются во временное хранилище.
  • В значениях свойств в которых используются переменные, подставляются значения из хранилища.
  • Свойства миксинов тоже сохраняются во временное хранилище.
  • В селекторы, внутри которых используются миксины, подставляются значения нужных свойств из хранилища.

Второй: запускается обход преобразованного дерева и происходит отрисовка тех узлов которые нужно отрисовать (только селекторы и их свойства).

После всех этих нехитрых манипуляций, мы получаем валидный css-файл.

Заключение

Конечно, данный функционал недотягивает до настоящего SASS и данная реализации менее стабильная в работе. Но данный опыт был интересным, небольшой персональный челлендж на проверку собственных сил. Особенно забавно не просто реализовывать сложный функционал, а делать это в соответствии со стайл-гайдами, благо есть такие инструменты как rubocop и rubycritic.

Весь исходный код выложен на github. Пример использования можно прочесть в README в разделе “Example of usage”. Исходные файлы хранятся в папке /source, скомпилированные файлы сохраняются в папку /css. https://github.com/kopylovvlad/lite_scss

Спасибо за время потраченное на прочтение данной статьи. “Knowledge must be shared”

--

--

Vladislav Kopylov
Vladislav Kopylov

No responses yet