Принялось решение добавить регулярные выражения в свой язык программирования. По началу я подумал, что мне совершенно незачем в них разбираться и в интернете, наверняка, уже есть полно готовых библиотек. Стал искать, нашёл какие-то осколки кода на С++, которые ничего не дают. Пришлось самому разобраться, что такое регулярные выражения тут. Ради спортивного интереса, я решил сделать свою библиотеку на С++.
Стал делать и подумал, а почему бы мне не добавить туда своих тараканов. Я решил добавить две конструкции:
{namesubexpression} - вызов подвыражения по имени "namesubexpression",
($namesubexpression:BodyExpression) - описание под выражения с именем "namesubexpression".
Само описание под выражения может встречаться в любом месте структуры регулярного выражения и игнорируется при поиске, подобно закоментированым: (#MeComment).
Сразу же возникает проблема бесконечной рекурсии.
Вот пример рекурсивного регулярного выражения, которое недопустимо: ($E:{E}){E}
Конечно, я сделал стадию валидации и такие поисковые конструкции просто не допустятся в поисковую машину. Также валидацию не пройдет выражение, которое содержит в себе вызов неописанного подвыражения.
Вот пример текста, который можно спарсить рекурсивным регулярным выражением (РРВ): [[[[[A]]]]]
А вот его РРВ: ($RRE:\[({RRE}|A)\]){RRE}
Я также решил добавить три зарезервированные конструкции:
{:String} соответствует выражению: (("(\\.|[^"])*")|('(\\.|[^'])*'))
{:Number} соответствует выражению: (-?[0-9]+\.?[0-9]*[Ee]?-?[0-9]*)
{:Name} соответствует выражению: ([A-Za-z][A-Za-z0-9]*)
Но их поисковая система не использует структурные элементы аналогичных выражений, а организованна встроенным машинным поиском, который работает значительно быстрее и возвращает одну целую строку текста, в которой содержится всё тело найденного соответствия а не части для каждого компонента в аналогичных регулярных выражениях.
Формат ответа:
Поисковая машина в случае обнаружения хотя бы одного соответствия выдаёт ответ. А именно структуру, которая является множеством элементов, каждый из которых может быть либо строкой текста, либо таким же множеством. Ответом успешного поиска гарантированно будет множество. Каждым элементом которого будет множество, которое гарантированно состоит из двух элементов. На первом месте гарантированно стоит строка текста: либо "Text", либо "Expression". Это признак порции ответа. В случае "Text", на втором месте гарантированно будет строка текста. В другом случае, на втором месте гарантированно будет описание найденных соответствий с элементами РРВ. Всякое найденное соответствие разделяет текст, в котором проводится поиск, на части, которые находятся между...
Та короче, вот пример:
Пример кода на языке Автор:
Expression = ":";
Data = "SomePrefixText :: SomeInteriorText : SomeSufixText.";
Result = Expression.RegularExpression("Parse",Data);
trace(JSON("stringify",Result));
Ответ:
[
[ "Text", "SomePrefixText " ],
[ "Expression", [ ":" ] ],
[ "Expression", [ ":" ] ],
[ "Text", " SomeInteriorText " ],
[ "Expression", [ ":" ] ],
[ "Text", " SomeSufixText." ]
]
Вот второй пример:
Expression = "($RRE:\\(({RRE}|A+)\\)){RRE}";
Data = "((AAAA))";
Result = Expression.RegularExpression("Parse",Data);
trace(JSON("stringify",Result));
Ответ:
[
[
"Expression",
[
[
"SubExpression:RRE",
[
"(",
[ [ [ "SubExpression:RRE", [ "(", [ [ [ "A", "A", "A", "A" ] ] ], ")" ] ] ] ],
")"
]
]
]
]
]
Теоретически, с помощью одного хорошо сделанного РРВ, можно описать синтаксис любого языка программирования, в котором нет предпроцессора.
Так выглядит PHP: ((<\?(php)?.*?\?>)+|(^<\?(php)?.*))
:-)
Но недостатком такого подхода является невозможность отследить синтаксические ошибки.
А вот ещё один пример.
Ради спортивного интереса я создал и протестировал РРВ, которым можно описать любое РРВ и само себя. Полагаю, вам будет интересно взглянуть:
(?x)
($CallSE:\{({:Name}|:(String|Number|Name))\})
($SpecialChar:\\[DSWdsw])
($SpaceChar:([ \f\n\r\t\v]|\\[fnrtv]))
($x:\\[0-9A-Fa-f]{2})
($O:\\[0-7]{3})
($onec:({x}|{O}|\\.|[^\[\]\\\^]))
($ones:({x}|{O}|\\.|[^\[\]\\\^$|?*+\(\){}]))
($Char:\[\^?({SpecialChar}|({onec}-{onec})|{onec})*?-?\])
($Qantifikator:([\?\*\+]|\{[0-9]*(,[0-9]*)?\})[\?\+]?)
($Grupa:\\[1-9])
($QE:\\Q.*?\\E)
($PosInStr:([\^\$]|\\[Bb]))
($PrefixBody:((\#|\?#)|(\${:Name}:)|(\?[ismx-]*:)|(\?=)|(\?!)|(\?<=)|(\?<!)|(\?=)|(\?>)|(\?)))
($SubE:\({BodyE}\))
($BodyE:(
(\?[ismx-]*)|
(
{PrefixBody}?
(
({SubE}|{Char}|{CallSE}|{Grupa}|{QE}|{PosInStr}|{SpecialChar}|{SpaceChar}|(\|)|{ones}){Qantifikator}?
)*
)
))
{BodyE}
Вы можете взглянуть на код этой библиотеки РРВ на С++ тут.
Кому надо, берите и пользуйтесь.
Помните: делать свой язык программирования - самая неблагодарная робота.
Но программист-пользователь ценится дороже, чем просто пользователь.
Жду ваших комментариев, критики и интересных предложений.
Комментарии (18)
codecity
21.01.2023 03:34+2Открыть в Visual Studio C++ 6
Дата релиза - 1998 год. Можно узнать из любопытства, она нормально устанавливается на Win 10?
KanuTaH
21.01.2023 03:45+12По началу я подумал, что мне совершенно незачем в них разбираться и в интернете, наверняка, уже есть полно готовых библиотек. Стал искать, нашёл какие-то осколки кода на С++, которые ничего не дают.
Регулярные выражения есть в std начиная с C++11, уже больше 10 лет тому как.
Открыть в Visual Studio C++ 6
А-а, вон оно чо, Михалыч.
pae174
21.01.2023 08:22+2{:Digit} соответствует выражению:
(-?[0-9]+.?[0-9]*[Ee]?-?[0-9]*)
При такой регулярке числом будет считаться даже текст "0<E-"
Ну и кроме того Digit - это отдельная цифра. А число это Number.
monstr0518 Автор
21.01.2023 13:29На машинном уровне это организованно чуточку иначе: https://github.com/monstr518/RegularExpressions/blob/main/RegularExpressions.cpp#L1878
Но минус действительно возможен лишний. Я могу это легко исправить, если хоть кому-то это нужно.
Ritan
21.01.2023 14:39+3Код - это ужас какой-то. Как будто библиотека напрямую из времён актуальной MSVS 6 прибыла.
using namespace std; typedef vector<string> V_S;
В заголовочном файле? Так не делали даже тогда.
class BaseFinder { public: int type;
Может всё-таки enum, который был ещё в с89?
bool SimvolFinder::isProbelSimvol(const char c){ if(!c)return 1; const char* chars = " \f\n\r\t\v"; int i; for(i=0;chars[i];++i)if(c==chars[i])return 1; return 0; }
И ещё, очень похоже, что это всё не будет работать с utf8 вообще никак
agalakhov
21.01.2023 15:36+2Грамматики, могущие содержать рекурсию, называются контекстно-свободными. Их теория и способы описания хорошо разработаны. Выражения, подобные регулярным, для них не используют, так как собственно регулярные выражения придуманы для регулярных грамматик, которые по определению не могут содержать рекурсии, и "расширение возможностей" превращает их в жуткий костыль. Для контекстно-свободных грамматик традиционно применяют намного более удобную и легко читаемую форму записи Бэкуса-Наура.
Zenitchik
21.01.2023 17:32Автор, похоже, независимо изобрёл формализм PEG (parsing expression grammar).
Это способ описания алгоритмов рекурсивного спуска в виде выражений, похожих на регулярные.0xd34df00d
21.01.2023 18:37Пеги мощнее, чем КС.
Zenitchik
21.01.2023 20:32Это можно строго доказать?
0xd34df00d
22.01.2023 10:43Смотря что конкретно вы понимаете под пегами (и мне стоило это сразу уточнить).
Если формализм из статьи Форда 20-летней давности — ЕМНИП (но это неточно) там можно парсить a^n b^n c^n, а это каноничный пример не-КС-грамматики. При этом вопрос о том, любая ли КС выразима в том конкретном формализме, насколько я знаю, открыт.
Если (и это привычная мне интерпретация) вы понимаете любые парсеры на комбинаторах а-ля parsec — да, строго мощнее. Там и КЗ, и КС выражаются явно.
0xd34df00d
21.01.2023 18:37Насчёт костыльности можно поспорить. Если делать правильно, то там все довольно изящно.
monstr0518 Автор
23.01.2023 01:36-1Вы мне ещё раз такое скажете, и я свой ScriptX опубликую. Это язык который со старта знает только описание конструкции БеконаСаируса (так чё-то). И он дополняет свой синтаксис, прямо в процессе исполнения кода :).
Zenitchik
Т.е. рекурсия всё-таки не реализована?
monstr0518 Автор
Реализована. Но допустимо так: ($OK:X{OK}?){OK} и находит оно: XXXXXXXX. А бесконечное вхождение без смещения каретки поиска недопустимо.
Zenitchik
Понял. Спасибо. А что под капотом? Рекурсивный спуск?
monstr0518 Автор
Да.