Неделя самописных языков разметки на Хабрахабре. Статья про AXON напомнила мне про мой проектик o.t — object template language. В нём я соединил интересные идеи из XML, YAML и прочих.


Что, ещё один?


Велосипеды бывают разные. Мне, например, было интересно попробовать создать именно язык описания данных и в какой-то степени язык разметки.


TL;DR


<note status="saved">
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
</note>

note:
  status :: saved;
  to: "Tove";
  from: "Jani";
  heading: 'Reminder';
  body: `Don't forget me this weekend!`;
;

Концепция


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


Второй вариант намного проще и вообще не про программирование как таковое. Он скорее про шаблоны в том виде, в котором они представлены в технологии JSP, когда xhtml перемешивается с управляющими элементами. При этом замечено, что jsp неплохо подходит для чистой шаблонизации без программирования. Но там же XML, великий и ужасный.


А что есть на замену кроме JSON, YAML и TOML (.ini на стероидах)? Первый описывает примитивные типы, объекты и массивы javascript, второй делает то же самое, но без курли-скобочек, а третий вообще не о том.


От XML их всех отличает только одно, они описывают объект через его свойства. Предполагается при этом, что при необходимости пользователь добавит объекту поле type и опишет класс объекта. В XML объект описывается непосредственно как экземпляр класса и потом при необходимости могут быть указаны дополнительные поля, типа идентификатора.


Что же будет, если объединить простоту YAML и концепцию объектов из XML? Правильно — object template language, экспериментальный человекочитаемый язык описания данных, структур и шаблонов.


O.T.


Схематически весь OT можно описать одной строчкой module~class(id): content; то есть это модульный язык рекурсивных объектных шаблонов. Кроме этого, присутствуют дополнительные типы данных помимо строк и отсутствуют ассоциативные массивы, которые типа объекты из JSON, как неконцептуальная сущность.


Помимо текстовых идентификаторов, строковых, булевых, целочисленных и вещественных констант присутствуют некоторые символы, позволяющие описывать древовидные структуры объектов (object model). Так же присутствует ссылочный тип для построения других видов структур-графов.


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


Объектная модель


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


module~class(id)

Например, для html-страницы существует объект класса body, который может быть предварен идентификатором модуля, из которого взят этот класс (html~body). Этот объект наделяется идентификатором index-page, по которому можно установить связь с объектом через ссылку @index-page.


body
html~body
html~body(index-page)
@index-page

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


html:
  body
;

Внутреннее содержимое объекта начинается после символа :. Всё содержимое вплоть до соответствующего символа ; будет считаться принадлежащим объекту-родителю. Если объект не предполагает содержимого, символы :; не указываются.


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


html:
  body: br br br;
;

Повторное использование


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


xml:
  attr ::
    id: "my-xml";
  ;
  attr ::
    xmlns: "urn:oberon:ot";
  ;
;

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


Другое содержимое


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


html:
  head:
    title: "Привет, Мир!";
  ;
  body:
    p: 2 "+" 2 "=" 4.0;
    concat: "Hello":':':"World":"!";
  ;
;

Текстовое содержимое указывается в кавычках разных типов: ', ", и `, при этом возможно указание отдельного символа текста в одинарных кавычках ' и с помощью шестнадцатеричного кода (например 0DU) с модификатором U на конце. Текст может содержать переносы строки и любые другие символы unicode. Есть возможность конкатенации строки и символов с помощью символа : расположенного между двумя и более частями строк. Результирующая строка будет представлена в объектной модели как одна строка.


Модульность


Модульность в самом простом виде позволяет визуально разделять одноименные классы объектов различной природы. Например, html~body и human~body. Модульность в объектной модели предполагает так же наличие поддержки контекстом некоторых модулей, расширенная функциональность которых позволяет расширить возможности работы с шаблоном в рантайме.


Стандартные модули и классы


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


core

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


core~template:
  (* ваше содержимое *)
;

Модуль core предоставляет класс import, который позволяет описывать зависимости шаблона от сторонних шаблонов (стандартных, пользовательских, виртуальных и т.д.).


core~template:
  import: context;
;

Внутри объекта типа core~template действует неявное указание модуля для известных классов, таких как import и для импортированных классов действует такое же правило, поэтому нет необходимости (но возможность остаётся) указывать модуль core и другие импортированные модули каждый раз.


Например:


core~template:
  import: html;
  html: body: p: "Привет, Мир!";;;
;

Классы html, body и p принадлежат модулю html и их принадлежность контролируется рантаймом, но при этом указывать их модуль явно не требуется.


Шаблонизация и пользовательские данные


context


Модуль core предоставляет класс context который является идентификатором модуля context.
Модуль context предоставляет доступ к данным контекста существования объектной модели. Обращение к объектам модели через специальные классы модуля context позволит размещать в содержимом шаблона данные из рантайма.


Например, класс context~$ позволяет обратиться к данным контекста по идентификатору или иерархическому набору идентификаторов.


core~template:
  import: context;
  $(hello-world-text)`, `$(user-names/hello-world-user)`!`
;

Таким образом, в зависимости от данных, размещенных в контексте каким-либо образом и доступных по заданным идентификаторам, после обработки текста шаблона и построения объектной модели мы получим итоговый объект с заданными значениями.


core~template:
  import: context;
  `Здравствуй` `, ` `Дружок` `!`
;

Реализация


Первый прототип парсера был написан на golang, рядом были реализованы некоторые шаблонные модули, core конечно, и ещё модуль хранения бинарной информации в виде zbase32-строки, отличный формат, заслуживает отдельного упоминания дизайн-документ этого формата, как тщательно люди анализировали ситуацию с форматами baseXXX.
В дальнейшем, был реализован неполный прототип парсера OT для javascript, который стал основой для языка описания структурных атомов в языке… а впрочем это уже другая история.


Ссылки


Поделиться с друзьями
-->

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


  1. Fedcomp
    07.07.2016 21:40

    Еще немного и получится ruby dsl.
    Пример dsl для html: https://github.com/activeadmin/arbre


    1. pengyou
      07.07.2016 22:28

      Templates suck

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


      1. pengyou
        07.07.2016 22:32

        Но потом понял, что почти всегда template рассматривается как плейнтекст со специальной разметкой для сокрытия или размножения готовых кусков текста (плюс извлечение данных из контекста аля ${user.name}). А если развить идею jsp и сначала превратить всё в объекты и только потом рендерить — возможно получится более понятная концепция. Что-то вроде mustache, но объектное и типизированное.


  1. maxru
    08.07.2016 11:02

    Object Template?
    Есть интересный проектик по схожей тематике (правда только для PHP) — https://github.com/facebook/xhp-lib
    Пишем XML, а это на самом деле не XML, а объекты.
    (вопрос на засыпку — как быстро оно выест всю доступную память?) :o)


    1. pengyou
      08.07.2016 11:44

      Мне кажется, или это похоже на react.js для php?


      1. maxru
        08.07.2016 13:10

        Это похоже на что угодно, что работает с DOM как с native-объектами.