После прочтения книги про регулярные выражения (далее просто РВ) у меня появились кое-какие мысли по поводу их читаемости. Когда РВ только появлялись, и в них было довольно мало условных обозначений вроде \d, \w и тому подобных, то, наверное, все было не так страшно, хотя уже тогда стоило задуматься о наглядности. Сейчас чтение кода с РВ — это тихий ужас. Нет, если РВ короткое, то особых проблем нет, но по мере их усложнения и появления различных скобок все становится просто кошмарно. Ситуация усугубляется тем, что в некоторых языках (не будем указывать пальцем) постоянно приходится удваивать слеши.

Кроме того в той нотации РВ, которая сейчас используется в большинстве языках программирования, в некоторых, казалось бы простых ситуациях, приходится выкручиваться с помощью различных финтов. Первый пример, что пришел в голову — составить регулярное выражение если «abc», то затем НЕ «xyz».



На мой взгляд пора уже отказаться от той нотации, которая сейчас используется, и создать новую, которая будет ближе к обычному языку программирования, ведь нотация РВ — это по сути и есть язык, но оформленный просто ужасно. Самое худшее, что есть в сегодняшней нотации — это обилие скобок вроде (...), (:...), (?:...), (?=...), (?!...), (?<=...), (?<!...), (?<). Именно благодаря ним выражения становятся запутанными и невозможно охватить взглядов все РВ, чтобы сразу сказать, что оно ищет, а приходится проверять каждый символ в строке, не забыв, например, что ^ в середине РВ — это начало строки, а в начале квадратных скобок [^...] — это инверсия. Ну неправильно то, что при появлении новой возможности разработчики создают новое обозначение, какое-нибудь (&^%$#@...), которое само по себе абсолютно ничего не говорит.

Ведь в чем прелесть обычных языков программирования (не беря какие-то крайние случаи)? Если мы видим оператор if или whileв незнакомом языке, то можем сразу сказать, что он примерно он делает. Да, можно заменить эти операторы на символы вроде @#$% и #&$^ соответственно, к ним можно даже привыкнуть, но как говорилось в анекдоте про урок русского языка в Грузии, «это надо запомнить, понять этого невозможно».

Возможно, ситуацию могли бы улучшить умные редакторы кода, которые в регулярном выражении по-разному подсвечивали бы скобки (?:...), (?=...) и т.д., чтобы сразу видеть области их действий, но для большинства языков программирования этого сделать почти невозможно, т.к. РВ там — это строка и редактор должен был бы уметь определять по содержимому строки, что перед ним: РВ или обычный текст. Да и все-равно при большой вложенности скобок РВ превратится в разноцветную радугу.

Вообще говоря, довольно неплохие изменения в плане читаемости РВ уже произошли благодаря появлению режимов, когда РВ записываются на нескольких строках, а также благодаря комментариям внутри РВ. Появилась даже конструкция с понятным видом (?if...), в Perl (не к ночи будет упомянут) в регулярное выражение можно встраивать программный код, а в .NET вместо простой замены по РВ заменяемое значение может генериться с помощью специально обученного специального делегата. В целом уже можно даже написать более-менее понятное РВ, но все-равно это не то, это больше похоже на костыли.

Уже пора создать язык РВ, похожий на остальные «человеческие» языки программирования, а не на Brainfuck. Тогда в нем можно было бы организовать понятную подсветку, подсказки а ля IntelliSense, а в будущем, возможно, и пошаговую отладку РВ.

Дальше хотелось бы показать какими бы мне хотелось видеть РВ.

Во-первых, их надо как-то отделить РВ от обычных строк. Понятно, что функции для их работы требуют именно строк, не уверен, что РВ стоит встраивать в сами языки, как это сделано в Perl, пусть остаются строками, но чтобы их как-то выделить внутри кавычек стоит использовать какие-нибудь дополнительные обозначения. Это может быть что угодно, например, вместо "\d\w" (для наглядности я не буду удваивать слеши) стоит использовать "!\d\w!" или "<\d\w>", тогда редактор сможет легко отличить РВ от строк. В дальнейшем я буду использовать запись "!...!", но это не важно, как и остальные обозначения, главное суть.

Во-вторых, РВ нужно записывать только в режиме, когда игнорируются пробелы и переводы строк, причем, чтобы отделить внутри выражения литералы, которые всегда остаются неизменными от конструкций самих РВ, литералы можно брать в кавычки (не важно какие). Например, вместо «abcd\d\wxyz» можно будет написать:

"! 'abcd'
\d\w
'xyz'
!"


Или даже "!'abcd' \d\w 'xyz'!"

Редактор кода здесь отдельно сможет подкрасить abcd и xyz. Возможно, стоит использовать знак "+", чтобы связать эти части. Так даже будет нагляднее: "!'abcd' + \d\w + 'xyz'!", т.к. отдельные части РВ больше разделяются визуально.

Вас может смутить то, что знак "+" сейчас используется в значении «1 или больше совпадений», но это не страшно, потому что в этом значении его никто использовать больше не собирается. Это же не логично. Есть же такие наглядные конструкции как {min, max}, давайте их использовать вместе с оператором "*". Оператор "*" стоит использовать как раз в значении «умножить», то есть выражение "!'abc' * 3!" означает, что строка 'abc' должна повториться 3 раза. РВ "!'abc' * {1, 3}!" означает, что 'abc' должна повториться от 1 до 3 раз. Аналогично можно использовать запись "!'abc' * {1, }!" в значении «1 и больше совпадений» вместо "+", а вместо оператора "*" писать: "!'abc' * {0, }!". А запись "!'abc' * {3, 3}!" равносильна той, что мы уже видели "!'abc' * 3!". Старый оператор "*" тогда будет заменен на выражение "!'abc' * {,} !".

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

Остается вопрос с тем, как обозначить оператор минимальный "*" (он же не жадный). Можно было бы использовать оператор деления, но это тоже не логично, поэтому можно записать прямо в виде "!'abc' min* 3 !". Здесь min* — это один оператор без пробела. Этот вариант записи мне не очень нравится, но по крайней мере он своим именем поясняет суть.

Большинство скобок стоит заменить на встроенные функции. Например, вместо "[abc]" стоит записывать в виде "!any (a, b, c)!", тогда можно будет таким же образом заменить выражение "(:abc)|(:xyz)" на "! any ('abc', 'xyz') !" и мы сможем избавиться еще и от оператора "|". В качестве параметров функции можно использовать РВ, например "! any (\d\w, 'abc') !".

Надо решить как поступать с простейшими выражениями вроде \w, \b, \d и т.п. С одной стороны, они довольно компактные, но мне, например, нравится запись, которая сейчас может использоваться в квадратных скобках — [:alnum:]. Для удобства можно заменить их на запись вида _alnum_. А может быть самые простейшие \d и \w стоит оставить как есть. А вместо ".", которая не особо видна в тексте можно использовать запись _any_. Те же пробелы и табуляции, которые игнорируются в самом выражении можно записывать в виде _space_ или просто брать их в кавычки.

Обязательно нужно ввести нормальный оператор if — then — else, суть которого заключается в том, что если выражение после if выполняется, то затем проверяется РВ в ветке then, иначе после ветки else. Думаю, что слово then можно опустить. Тогда можно будет составить такое РВ:

"! 'abc'
if (\w * 3)
{
'xyz'
}
else
{
\d * {1, } 'klmn'
}
!"


Здесь я использовал синтаксис как в С-подобных языках, но это не критично. Дословно это выражение обозначает: Сначала идет строка 'abc', затем проверяется РВ '\w * 3', если оно выполняется, то затем должно идти 'xyz', в противном случае должно идти как минимум одно число, а затем 'klmn'.

Может быть, даже стоит ввести операторы типа case, while и for. Кроме того нужно ввести логические операции И, ИЛИ, НЕ, чтобы их использовать в условии. Не уверен на счет И и ИЛИ, ведь выражение "! if ('abc' && 'xyz') !" равносильно "! if ('abcxyz') !", а "! if ('abc' || 'xyz') !""! if (any ('abc', 'xyz') ) !". Но оператор отрицания нужен точно, чтобы определять что в данном месте не должно находиться.

Нужно ввести переменную, которая обозначает позицию в строке, где сейчас осуществляется поиск (пусть будет переменная _pos_), а так же переменная, хранящая саму строку, к которой применяется РВ (_this_). Тогда оператор "^" можно заменить более понятным "! _pos_ == 0 !", а "$" на "! _pos_ == (strlen(_this_) — 1) !" Может быть стоит ввести отдельное обозначение для конца строки, например, по аналогии с Python: _pos_ == -1. Эти же переменные позволят сделать опережающую и ретроспективную проверку.

Нужно оставить комментарии. Как они будут выглядеть это уже не важно.

Оператор присваивания должен работать в двух режимах. Первый — это проверка и присваивание переменной строки, соответствующей регулярному выражению, то, для чего сейчас используется запись вроде "(?<foo>...)": "!foo = \w\d*; !". Точку с запятой придется использовать, чтобы показать где кончается оператор присваивания.

Второй режим присваивания — сохранение регулярного выражения без его проверки. Используется для наглядности, например,

"!
foo = !\d\w*!

'abc' foo 'xyz' foo
!"


Здесь выражение !\d\w*! (обратите внимание на восклицательные знаки) используется затем по имени переменной foo.

Это основные идеи, которые появились относительно РВ. Было бы интересно попробовать такие выражения в деле, но, к сожалению, руки до реализации такого парсера у меня вряд ли дойдут. А вообще, начать можно было бы с того, что такие выражения преобразовывались к классическому виду РВ, а затем обрабатывались бы готовой библиотекой.

В завершении маленький пример для поиска URL. Возможно, там не все учел, например, считается, что доменная зона может быть только com, net, info или двухбуквенная.

"!
unicode = !% any(\d, A-F) * 2 ! // Представление Unicode в адресе.
// Переменная только создается, но не проверяется
domain = !any ('com', 'net', 'info', (a-z) * {1, 2})!
host = !any (\w, '_', unicode)!

"http://" (host '.') * {1,} domain '/' * {0, 1}
"!

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

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