В прошлой статье мы рассказали про придуманную нами систему обозначений для дыхательных “движений” и в ней мы показали интересную систему, которая не связана с программированием и D. На самом деле – эта система изначально задумывалась и как средство написания “программ для мозга” (с некоторыми доработками, если захотите, то расскажем подробнее отдельно), и как набор символов, который можно быстро разобрать с помощью тривиального скрипта.
И о том, как сделать разбор без библиотек, кроме стандартной и пойдет речь далее…
Вся система описана в статье, и с тех пор у нее есть продолжение, которое описывает не только дыхание. Основой системы служит небольшой набор правил, самым важным из которых является следующее: есть основные символы, а есть модификаторы, которые изменяют смысл прочтения основных. Главной проблемой при такой простот является тот факт, что основные символы могут быть представлены более чем одним знаком.
Смотрите сами, вот все основные символы:
▲ - вдох ▼ - выдох ▶ - пауза N - носовое дыхание M - ротовое дыхание D - диафрагмальное дыхание + для паузы на вдохе - для паузы на выдохе
Стоит обратить внимание, что символы условно поделены на три группы. Только символы из первой группы являются обязательными. Символы остальных групп служат уточняющими, но их использование создает один цельный символ из двух основных. Модификаторы в этом смысле устроены значительно проще – это всегда один символ, который идет после основного символа. И модификаторы не входят в основной набор.
Сами формулы, несмотря на простоту, не всегда легко читаются людьми, и поэтому возникла идея сделать ряд простых функций, которые позволят разбивать “дыхательные формулы” на смысловые части. Эти смысловые части мы назовем токенами и каждый из них будет представлять собой осмысленный символ из формулы.
Представим, что формула задается в виде строки без каких-либо разделителей или пробелов, тогда функция разбиения формулы на токены будет выражена в таком коде:
import std.string; import std.algorithm; import std.array; import std.conv; import std.regex; import std.stdio; struct BreathingToken { string symbol; string modifier; string duration; } BreathingToken[] parseBreathingFormula(string formula) { BreathingToken[] tokens; auto tokenRegex = regex(r"([NMD]?[▲▼▶])([+\-~_*/\\↑↓])?(\([^)]+\))?|●"); foreach (match; matchAll(formula, tokenRegex)) { if (match[0] == "●") { tokens ~= BreathingToken("●", "", ""); } else { BreathingToken token; token.symbol = match[1]; if (match[2] != "") { token.modifier = match[2]; } if (match[3] != "") { token.duration = match[3][1..$-1]; // Удаляем скобки } tokens ~= token; } } return tokens; }
Определяем структуру BreathingToken, которая накапливает символы и содержит следующие поля: symbol – символ токена, modifier – модификатор и duration – продолжительность.
Из всех описанных частей со всем, кроме продолжительности, мы уже разобрались. Продолжительность устроена тривиально: это число после основного символа или модификатора, заключенное в круглые скобки.
Функция parseBreathingFormula принимает строку formula и возвращает массив токенов типа BreathingToken. Поэтому начинается функция с задания накопителя токенов tokens и регулярного выражения tokenRegex, применяемого для разбора формул. Это регулярное выражение состоит из нескольких частей:
([NMD]?[▲▼▶])
: ищет символы N, M или D (необязательные), за которыми следует один из символов ▲, ▼ или ▶;([+\-~_*/\\↑↓])?
: ищет необязательный модификатор;- ($$[^)]+$$)?: ищет необязательную продолжительность в круглых скобках;
- |●: также ищет символ ● как отдельный токен.
Далее с помощью matchAll находим все совпадения в строке формулы и далее действуем в зависимости от того, что попало в совпадение. Если найденный символ равен “●” (это конец цикла, особый стоп-идентификатор в формуле), то добавляется новый токен с символом “●” и пустыми значениями для модификатора и продолжительности. Если же в совпадении что-то иное, то создается новый токен: поле symbol заполняется значением из первого захватывающего подвыражения, а если находится модификатор, то он добавляется в поле modifier. В случае, если есть продолжительность, то она извлекается из скобок и добавляется в поле duration. После этих действий, обнаруженный токен добавляется в массив токенов.
Сама функция parseBreathingFormula код позволяет разбивать строку формулы дыхания на отдельные токены с соответствующими символами, модификаторами и продолжительностью. Он использует регулярные выражения для извлечения информации из строки и организует ее в удобную структуру данных для дальнейшего использования
Сама функция простая, но ее использование можно сделать интереснее, если совместить ее применение с функцией генерирующей описание для каждого токена:
string breathingFormulaToText(string formula) { auto tokens = parseBreathingFormula(formula); string[] descriptions; foreach (token; tokens) { string description; // Описание символа switch (token.symbol) { case "▲": description = "вдох"; break; case "▼": description = "выдох"; break; case "▶": description = "пауза"; break; case "●": description = "конец цикла"; break; default: if (token.symbol.length > 1) { switch (token.symbol[0]) { case 'N': description = "носовой "; break; case 'M': description = "ротовой "; break; case 'D': description = "диафрагмальный "; break; default: break; } description ~= breathingFormulaToText(token.symbol[1..$]); } break; } // Описание модификатора if (token.modifier != "") { switch (token.modifier) { case "+": description ~= " на вдохе"; break; case "-": description ~= " на выдохе"; break; case "~": description ~= " (глубокий)"; break; case "_": description ~= " (прерывистый)"; break; case "*": description ~= " (с акцентом)"; break; case "/": description ~= " (ускоряющийся)"; break; case "\\": description ~= " (замедляющийся)"; break; case "↑": description ~= " (увеличивающийся по глубине)"; break; case "↓": description ~= " (уменьшающийся по глубине)"; break; default: break; } } // Описание длительности if (token.duration != "") { description ~= " (" ~ token.duration ~ " сек)"; } descriptions ~= description; } return descriptions.join(", "); }
Функция breathingFormulaToText создает упрощенное текстовое описание для формулы, представляющее собой по сути пошаговый алгоритм ее выполнения. Описание работы этой функции мы не приводим, поскольку это простой вариант и его работа тривиальна. Также мы надеемся на то, что ее можно улучшить (и мы надеемся без ИИ).
Подный тестовый код для испытаний:
import std.string; import std.algorithm; import std.array; import std.conv; import std.regex; import std.stdio; struct BreathingToken { string symbol; string modifier; string duration; } BreathingToken[] parseBreathingFormula(string formula) { BreathingToken[] tokens; auto tokenRegex = regex(r"([NMD]?[▲▼▶])([+\-~_*/\\↑↓])?(\([^)]+\))?|●"); foreach (match; matchAll(formula, tokenRegex)) { if (match[0] == "●") { tokens ~= BreathingToken("●", "", ""); } else { BreathingToken token; token.symbol = match[1]; if (match[2] != "") { token.modifier = match[2]; } if (match[3] != "") { token.duration = match[3][1..$-1]; // Удаляем скобки } tokens ~= token; } } return tokens; } string breathingFormulaToText(string formula) { auto tokens = parseBreathingFormula(formula); string[] descriptions; foreach (token; tokens) { string description; // Описание символа switch (token.symbol) { case "▲": description = "вдох"; break; case "▼": description = "выдох"; break; case "▶": description = "пауза"; break; case "●": description = "конец цикла"; break; default: if (token.symbol.length > 1) { switch (token.symbol[0]) { case 'N': description = "носовой "; break; case 'M': description = "ротовой "; break; case 'D': description = "диафрагмальный "; break; default: break; } description ~= breathingFormulaToText(token.symbol[1..$]); } break; } // Описание модификатора if (token.modifier != "") { switch (token.modifier) { case "+": description ~= " на вдохе"; break; case "-": description ~= " на выдохе"; break; case "~": description ~= " (глубокий)"; break; case "_": description ~= " (прерывистый)"; break; case "*": description ~= " (с акцентом)"; break; case "/": description ~= " (ускоряющийся)"; break; case "\\": description ~= " (замедляющийся)"; break; case "↑": description ~= " (увеличивающийся по глубине)"; break; case "↓": description ~= " (уменьшающийся по глубине)"; break; default: break; } } // Описание длительности if (token.duration != "") { description ~= " (" ~ token.duration ~ " сек)"; } descriptions ~= description; } return descriptions.join(", "); } void main() { string formula = "N▲*(1)▶+(2)M▼↑(4)●"; auto tokens = parseBreathingFormula(formula); writeln("Разбор формулы: ", formula); foreach (token; tokens) { writeln("Символ: ", token.symbol, ", Модификатор: ", token.modifier, ", Длительность: ", token.duration); } auto frms = [ `M▲M▼▶M▲M▼▶●`, `N▲▶+M▼▶●`, `▲~▼▶●`, `▲▶+▼▶●`, `▲▼▶▲▼▶▲▼▶●`, `▲▼▶●▶▶●`, `D▲D▼▶●` ]; foreach (frm; frms) { writefln( `%s = {%s}`, frm, frm.breathingFormulaToText ); } }
Для чего все это ? Мы придумали это для упрощения дыхательных техник (и не только), а также для каталогизации и поиска интересных психотехник. Это может помочь многим, а также, как мы надеемся позволит толкнуть вперед новый тип технологий. И парсинг формул это только начало данного пути и есть множество идей о том, как его продолжить…