Меня зовут София. Я - CSS инженер. Это мой блог.

Если вам очень больно без стилей, вы можете их.

VDS (value definition syntax)

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

Немного формальной грамматики

Простой пример

Попробуем описать грамматику отрицательных чисел от -1 до -9 в такой форме:

<neg_num> ::= "-" <num>
<num> ::= "1" | "2" | "3" | "4" | "5" |
        | "6" | "7" | "8" | "9"

Обозначим отрицательное число токеном <neg_num> и определим его как символ -, сразу за которым следует цифра <num>. Ниже раскроем токен цифры как один символ из данного набора: 1, или 2, или 3...

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

<add_expr> ::= <neg_num> "+" "(" <neg_num> ")"

В этих записях мы видим некоторые символы, записанные в кавычках, которые попадают в конечное выражение без изменений. И что-то вроде переменных, которые с помощь заданных правил можно на эти символы заменить. Так вот, символы называют терминалы, а "что-то вроде переменных" - нетерминалы. Более формальное определение:

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

Форма Бекуса-Наура

Форма записи, которую мы использовали, называется Форма Бэкуса-Наура. Это формальная система описания синтаксиса, в которой одни синтаксические категории последовательно определяются через другие.

Запись синтаксического правила разбита на две части символом определения ::= или иногда вместо него используют ->. В левой части содержится определяемый нетерминал, а справа последовательность из терминалов и нетерминалов. В описании правила в правой части может использоваться оператор “|”, обеспечивающий логическое или. В описании правила может применяться рекурсия. В общем:

"text" или 'text' терминал
<A> нетерминал
<A> ::= <B> <A> заменимо на <B>
<A> <B> конкатенация
<A> | <B> один из элементов, “или”
integer имя класса нетерминалов, описывается не через BNF

Зная это, попробуем описать грамматику арифметических операций над натуральными положительными числами без скобок:

<expression> ::= <add_expr> | <mul_expr>
<add_expr> ::= <mul_expr> '+' <add_expr>
             | <add_expr> '+' <mul_expr>
             | <mul_expr> '-' <add_expr>
             | <add_expr> '-' <mul_expr>
<mul_expr> ::= integer '*' <mul_expr>
             | integer '/' <mul_expr>
             | <mul_expr> '*' integer
             | <mul_expr> '/' integer
             | integer
integer ::= [0-9]+

Поясним:

Расширенная форма Бекуса-Наура

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

[<A>] опциональное вхождение
(<A> | <B>) <C> группировка
{<A>} повторение элемента (0 или более раз)

Совокупность правил в этой таблице и БНФ называется расширенной формой Бекуса-Наура. Используем ее чтобы записать грамматику арифметикии компактнее:

<expression> ::= <add_expr> | <mul_expr>
<add_expr> ::= (<mul_expr> | <add_expr>) ('+' | '-') (<mul_expr> | <add_expr>)
<mul_expr> ::= (integer | <mul_expr>) { ('*' | '/') (integer | <mul_expr>) }
integer ::= [0-9]+

Ближе к CSS

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

<style-rule> ::=
  <selectors-list> '{' <properties-list> '}'
<selectors-list> ::=
  <selector>[':' <pseudo-class>] ['::' <pseudo-element>]
  [',' <selectors-list>]
<properties-list> ::=
  [<property> ':' <value>] [';' <properties-list>]

Проверим что эта запись соответствует дейвительности. Разберем примеры использования нескольких токенов.

Попробуем описать грамматику свойства margin. Его значение это 4 токена <length>, или ключевое слово auto, или одно из трех CSS-wide keywords : initial, unset, inherit, которые мы не будет включать в запись, поскольку непосредственно к свойству они не относятся. Например:

margin: 0;
margin: 0 -7px;
margin: 0 -7px 0;
margin: 0 -7px 0 2px;
margin: auto;

То есть, используя БНФ:

<margin> ::= "auto"
   | <length>
   | <length> {" "} <length>
   | <length> {" "} <length> {" "} <length>
   | <length> {" "} <length> {" "} <length> {" "} <length>

Выглядит громозко.

Синтаксис определения значения

По этим и некоторым другим причинам в 1998 году (примерно) на основе расшренной БНФ был создан Value definition syntax ( агнл. синтаксис определения значения, в дальнейшем VDS).

В нем определены следующие сущности и грамматики:

initial ключевые слова
<length> простые типы данных
<'line-height'> сложные типы данных (именуются обычно по свойству)
[<A> | <B>] <C> группировка
<A> || <B> || <C> один или более из группы в заданном порядке
<A> && <B> && <C> один или более из группы в любом порядке
<A>* ноль или более раз
<A>+ один или более раз
<A>? необязательный
<A> {n} А повторяется n раз
<A> {n, m} А повторяется не меньше n раз и не больше m раз
<A># А повторяется один или более раз, разделенных запятыми

В последней версии спецификации есть еще скобочная нотация диапазона (bracketed range notation). Для свойств числового типа с использованием обозначения диапазона она записывается в квадратных скобках [min, max] сразу после ключевого слова идентификации, что указывает на замкнутый диапазон между (включительно) min и max. Например, <integer [0,10]> указывает целое число от 0 до 10 включительно.

Проверим что он удобен для описания CSS на примере margin:

<'margin'> ::= "auto" | <length> {1, 4}

Удобно, ёмко, понятно, красиво 💅 ✨

Свойства посложенее тоже:

  1. Свойство толщины границы border-width:

    border-width: 3px;
    border-width: thick;
    border-width: medium thick;
    border-width: 8em thick;
    

    Запишем его грамматику:

     <'border-width'> =
       [ <length> | thick | medium | thin ]{1,4}
    
  2. Свойство текстовой тени text-shadow:

    text-shadow: none;
    text-shadow: 1px 1px 2px black;
    text-shadow: inset 1px 1px 2px black;
    text-shadow: inset 1px 1px 2px black,
    0 0 1em red, 0 3vw green;
    

    Запишем его грамматику:

     <'text-shadow'> =
       [ inset? [ <length>{2,4} <color>? ]# ] | none
    

Источники

  1. Спецификация css-values-4, раздел value definition syntax