Справочник синтаксиса Spintax

Полный справочник по формату spintax, он же spin syntax: каждая конструкция, её правила и её ловушки.

Перечисления {a|b|c}

Случайным образом выбирает один вариант из списка.

{option1|option2|option3}

Примеры

{blue|grey|clear}
{|free|paid} plan                 ← empty option = sometimes nothing
{Acme {Pro|Lite}}                ← nested enumerations
{order {|#42-A} confirmed}       ← nesting with empty option

Правила

  • Разделители: { и }
  • Разделитель вариантов: |
  • Поддерживает вложенность произвольной глубины
  • Пустые варианты допустимы (дают пустую строку)
  • Разрешение идёт от самого внутреннего выражения наружу

Перестановки [a|b|c]

Выбирает N элементов, перемешивает их и объединяет с разделителями.

Простые перестановки

Все элементы включены, разделены пробелами:

[1|2|3|4]

Примеры результата: 1 4 3 2, 2 3 4 1, 3 2 4 1

С разделителем

Единый разделитель указывается в < > в начале:

[<, > 1|2|3|4]

Примеры результата: 2, 1, 4, 3 · 4, 3, 2, 1

Важно: Нет пробела между [ и <разделитель>.

Разделители для каждого элемента

У каждого варианта может быть свой разделитель, заданный через <sep> перед предшествующим |. Разделитель перемещается вместе с элементом при перемешивании.

[<, > 1|2|3 < and >|4]

Примеры результата: 1, 3, 2 and 4 · 3, 1, 2 and 4

Авто-пробелы: Словесные разделители, такие как <и> или <или>, автоматически дополняются пробелами: <и> даёт  и . Знаки препинания (<,>) не дополняются.

Перестановки с комбинациями

Настраиваемое мин./макс. количество элементов и разделители:

[<minsize=1;maxsize=3;sep=", ";lastsep=" and "> apple|plum|orange|apricot]

Примеры результата: apple, plum and orange · apple and apricot · orange

Параметры конфигурации

ПараметрПо умолчаниюОписание
minsizeколичество всехМинимальное количество выбираемых элементов
maxsizeколичество всехМаксимальное количество выбираемых элементов
sep" " (пробел)Разделитель между элементами (кроме последнего)
lastsepкак sepРазделитель перед последним элементом

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

  • Разделители: [ и ]
  • Блок конфигурации <...> должен следовать сразу за [
  • Параметры конфигурации разделяются точкой с запятой
  • Строковые значения в конфигурации берутся в кавычки: sep=", "
  • Перечисления и перестановки могут быть вложены в варианты
  • HTML-элементы могут быть вариантами
  • sep соединяет всё до последней пары, lastsep — саму пару: при двух выбранных элементах появляется только lastsep, при одном — ни тот, ни другой

Переменные %var%

Определяет переменную для многократного использования, которая подставляется везде, где встречается. Объявить её можно двумя директивами, и выбор не косметический: #set — макрос, его значение подставляется заново при каждой ссылке, и spintax внутри перекатывается; #def раскатывает значение один раз за рендер и отдаёт этот единственный результат всем ссылкам. (Один рендер — это один вывод; тот же seed воспроизводит его.) Пока значение — простой текст, разницы нет; она появляется в тот момент, когда внутри появляется выбор.

#set %VARIABLE_NAME% = value or spintax structure

#def %VARIABLE_NAME% = value or spintax structure

Примеры

#set %name% = John
#set %greeting% = {Hello|Hi|Hey}
#set %items% = [<minsize=2;maxsize=3;sep=", ";lastsep=" and "> apples|oranges|bananas]
Some text with %name% and %greeting%, also %items%.
And once more, %greeting% — a second reference.

/# %greeting% above may differ between the two references — #set re-rolls.
   #def picks once and keeps it: #/
#def %tone% = {friendly|warm|upbeat}
A %tone% intro, and a %tone% outro — always the same word.

Правила переменных

  • #set и #def должны начинаться с начала строки
  • Имена переменных заключаются в %: %name%
  • Имена состоят из букв, цифр и подчёркиваний, а ссылки нечувствительны к регистру: %Tone% и %tone% — одна переменная
  • Значения могут содержать любой синтаксис spintax (перечисления, перестановки, другие переменные)
  • #set — макрос: раскрывается при обращении, а не при определении, и его значение — вместе со spintax внутри — подставляется заново и перекатывается при каждой ссылке
  • #def раскатывает значение один раз за рендер и держит результат везде. Именно этим держатся повторы: существительное и построенные от него падежные формы, число для блока {plural}, любая фраза, которая обязана повторяться слово в слово
  • #def делает переменную согласованной с самой собой, но не связывает две переменные. #def %Noun% и #def %NounGen% — два независимых раската, они могут выпасть на разные слова: формы, которые обязаны согласоваться, должны идти из одного раската — одна основа в #def, на которую ссылается каждая падежная форма, либо синонимы, склоняющиеся одинаково, с окончанием, вынесенным за определение
  • Имя определяется один раз. Второе определение того же имени помечается как definition.duplicate-name, но рендер всё равно идёт: между двумя одинаковыми директивами побеждает последняя, а если имя делят #set и #def, побеждает #def — независимо от того, какая из них шла первой
  • Ссылка без определения печатает саму себя: %missing% остаётся в выводе, а не исчезает
  • Ни одна из директив не переходит через #include: включённый шаблон не видит локальных переменных родителя, а его собственные не утекают наверх. Раскатанная форма попадает внутрь только как переменная времени выполнения
  • Строки #set и #def удаляются из результата
  • Подробно: см. руководство по переменным — области видимости и подвох с перекатом, и грамматически безопасную синонимизацию — падежные семьи

Области видимости переменных

Хост может подавать переменные из нескольких мест. Если одно и то же имя есть в нескольких, побеждает сильнейшее:

  1. Переменные времени выполнения (сильнейшие) — то, что хост передаёт в вызов рендера: context в @spintax/core, атрибуты шорткода в плагине WordPress: [spintax slug="greeting" name="Alice"]
  2. Локальные переменные — объявленные через #set или #def внутри шаблона
  3. Глобальные переменные (слабейшие) — значения по умолчанию на уровне хоста, например страница настроек плагина

Условия {?VAR?then|else}

Условный синтаксис — собственная конструкция языка: в прототипе GTW ничего подобного не было. Если {a|b} — это равномерный случайный выбор, не смотрящий на переменные, то {?VAR?then|else} выбирает по тому, есть ли значение у %VAR%.

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

Предварительный проход отрабатывает до раскрытия %var% и до случайного выбора веток, поэтому falsy-ветка отбрасывается полностью — ничего внутри неё не вычисляется.

Формы

{?VAR?then}                ← truthy ⇒ then; falsy ⇒ empty
{?VAR?then|else}           ← truthy ⇒ then; falsy ⇒ else
{?!VAR?then|else}          ← inverted
{?HasFreeTier? — free tier available since %founded%|, trusted since %founded%}

Truthy и falsy

Правило проще, чем в JavaScript — truthy = хотя бы один непробельный символ:

Значение %VAR%Truthy?
не объявленаfalsy
пустая строкаfalsy
только пробелыfalsy
"0", "false"truthy (непустые строки)
любой другой текст или HTMLtruthy

Правила условий

  • Имена переменных соответствуют тому же regex, что и %var% (регистронезависимы)
  • Префикс ! инвертирует проверку: {?!VAR?нет данных}
  • Первый | на нулевой глубине разделяет then и else; последующие | остаются литералами в else
  • Вложенные условия раскрываются outer-first — falsy-ветки короткозамыкаются
  • Составная логика (&&, ||, сравнения) не поддерживается — считайте guard-переменную в ассемблере
  • Кривые формы ({??yes}, {?VAR}) не падают — песочница отмечает их предупреждениями
  • Подробно: см. руководство по условному spintax с примерами и анти-паттернами

Плуралы {plural %n%: язык|языка|языков}

Подбирает грамматически верную форму слова под число. Счётчик идёт до двоеточия, формы — после, через |.

Форму выбирает локаль рендера, а не шаблон, поэтому количество форм зависит от локали: русскому нужно три, английскому — две.

{plural %n%: form1|form2}          ← 2-form locale (en, de, es…)

{plural %n%: form1|form2|form3}    ← 3-form locale (ru, uk, sr…)
#def %LangCount% = 5
supports %LangCount% {plural %LangCount%: language|languages}
← supports 5 languages

Формы по локалям

Локаль сопоставляется по языковому субтегу, так что ru-RU и ru ведут себя одинаково:

ЛокальФормВыбор по числу
ru, uk, be, sr, hr, bs31 · 2–4 · 5 и больше
все остальные, включая en2ровно 1 · всё остальное

Если форм не столько, сколько нужно локали, движок отдаёт plural.arity и оставляет блок видимым в полноширинных скобках — молча неправильное склонение в продакшен не уедет.

Правила плуралов

  • Открывающая часть литеральна, вместе с пробелом: {plural . {plural: x} и {pluralN: x} плуралами не являются
  • Двоеточие обязательно — оно отделяет счётчик от форм
  • Счётчик — это ссылка %Var% или целочисленный литерал; переменные в счётчике подставляются до выбора формы
  • Отрицательные числа берутся по модулю; 0 получает форму «всё остальное» (по-русски — «языков»)
  • Переменная-счётчик должна быть #def, а не #set#set это макрос, поэтому значение вида {1|4|9} на момент выбора формы всё ещё неразрешённый spintax, и блок отрендерится пустым. В песочнице это plural.count-macro
  • Нечисловой или неопределённый счётчик стирает блок, а не угадывает форму
  • Подробно: см. руководство по плуралам — русские правила трёх форм и разборы примеров

Включения #include

Встраивает другой шаблон в позицию директивы. #include — единственная конструкция, которую движок не может обеспечить сам: хранилища шаблонов у него нет, поэтому резолвер, превращающий ссылку в текст шаблона, даёт хост. Там, где резолвер не установлен — в песочнице и на MCP-сервере этого сайта, и то и другое намеренно, — директива инертна и остаётся в выводе обычным текстом.

#include "hero-text"

/# wrong: text before the directive on the same line leaves it literal #/
Intro: #include "hero-text"

Правила включений

  • Директива должна занимать всю строку. Отступ слева допустим, текст после ссылки — нет: Текст #include "hero" остаётся литералом
  • Ссылка берётся в двойные кавычки; одинарные кавычки или их отсутствие — уже не директива
  • Разрешение ссылки — дело хоста: плагин WordPress ищет по slug или числовому ID, JavaScript-хост передаёт includeResolver
  • Без резолвера строка остаётся в выводе литералом; если у резолвера такого шаблона нет, строка вместо этого удаляется — неизвестная цель молча стоит вам целого блока
  • Включённые шаблоны могут содержать свои переменные и spintax, а также свои #include
  • Включения разрешаются после того, как в родителе разыграны перечисления и перестановки: include в победившей ветке встраивается, а в отброшенной не случается вовсе
  • Цепочки работают (шаблон включает шаблон, который включает следующий); шаблон, включающий сам себя — напрямую или по циклу, — обрывается на первом повторе, без ошибки и без диагностики
  • Дочерние шаблоны наследуют глобальные переменные и переменные времени выполнения, но не локальные #set / #def родителя, и свои наверх не отдают
  • Включение не может быть значением определения: #def %x% = #include "y" отклоняется как def.include-in-value
  • До рендера validate() сообщает о неизвестной цели, только если хост передал список известных ссылок; extract() возвращает ссылки, нужные шаблону, — так хост их предзагружает
  • Подробно: см. руководство по композиции шаблонов — паттерн ассемблера, которым пользуется большинство пайплайнов

Комментарии /#...#/

Текст между маркерами комментариев удаляется из результата до любой другой обработки.

/#
  This is a comment section.
  It can span multiple lines.
  It won't appear in output.
#/

Правила комментариев

  • Открывающий разделитель: /#
  • Закрывающий разделитель: #/
  • Может занимать несколько строк
  • Вложенность не поддерживается
  • Удаляются до начала любой другой обработки

Вложенность

Все элементы синтаксиса могут быть вложены друг в друга на произвольную глубину:

{option1|[<, > sub1|sub2|sub3]|option3}

[<minsize=2;maxsize=3;sep=", ";lastsep=" and "> {red|blue} apples|{big|small} oranges|bananas]

#set %var% = {a|[b|c]}

Постобработка

Движок автоматически корректирует текст после генерации:

  1. Защита URL, email-адресов, доменов, десятичных чисел и аббревиатур от изменения регистра
  2. Удаление дублирующихся пробелов и табуляций
  3. Удаление пробелов перед знаками препинания (, . ! ?)
  4. Добавление пробела после знаков препинания, где он отсутствует
  5. Заглавная буква в начале результата (с пропуском HTML-тегов)
  6. Заглавная буква после знаков конца предложения
  7. Заглавная буква после блочных HTML-тегов
  8. Заглавная буква после переносов строк
  9. Восстановление защищённых заполнителей

Сводка синтаксиса

ВозможностьСинтаксисПоведение
Перечисление{a|b|c}Выбрать один случайный вариант
Перестановка[a|b|c]Выбрать N, перемешать, объединить
Разделитель[<sep> a|b|c]Перестановка с единым разделителем
Разделитель элемента[<, > a|b <x>|c]Перестановка с пользовательскими разделителями
Комбинации[<config> a|b|c]Перестановка с мин./макс. количеством
Переменная#set %var% = {a|b}Подставляется заново при каждой ссылке — spintax внутри перекатывается
Переменная (раз за рендер)#def %var% = {a|b}Один раскат за рендер, результат держится везде — так согласуются формы и окончания
Условие{?VAR?then|else}then, если truthy; иначе else
Плурал{plural %n%: язык|языка|языков}Согласует форму слова с числом по локали
Включение#include "slug"Встраивание другого шаблона — ссылку разрешает хост
Комментарий/#...#/Удаляется из результата

Язык вырос из своего прототипа — Generating The Web (GTW); шаблоны, написанные для GTW, по-прежнему работают без изменений.