Мой первый Pet-проект - Regexoop
Ссылка на проект Regexoop
Немного о себе. Я всегда был противником бесплатного труда, меня так воспитали. Работая только за деньги и только с чужими проектами я погорел. Это ужасное чувство тошноты. Я пробовал делать проекты для себя, но ни с кем не делился. Мне казалось, мою идею украдут, хотя идеи были на уровне игр про караваны. Я пытался делить шкуру неубитого медведя со своим эго. Оно побеждало.
Спустя некоторое время я увидел интересный метод "12 проектов за 12 месяцев". Суть метода состоит в том, что ты каждый месяц делаешь 1 продукт и показываешь его людям. Неважно насколько он готов. Ты можешь эти продукты выпускать как хочешь, хочешь за деньги, хочешь бесплатно. Я выбрал путь для этого проекта - бесплатно.
Суть проекта
Regexoop - переосмысление подхода написания regex. 30 сентября я подумал - "Хм, regex такой сложный. НУ ПОЧЕМУ? Может его можно сделать как-то проще?". Начав ровно 1 октября, я 3 дня писал документацию. Этот объем документации написанной за 3 дня, я разрабатываю уже 3 недели и не дошёл даже до половины. Чего уже говорить о настоящей мощи regex.
Концепт кода:
var rules = new {
pattern = "{second_level}{first_level}",
start = "end",
first_level = new {
pattern = "[a-z]",
minLength = 2,
maxLength = 3,
},
second_level = new {
pattern = "[a-zA-Z0-9-].", //а вот тут ошибка, но это ведь набросок
minLength = 2,
maxLength = 63,
maxRepeat = 5,
minRepeat = 0,
symbolsRules = new {
"-" = new { repeat: 0 }
}
}
};
var er = new Regexoop(rules).Input("https://docs.microsoft.com").Find()
Console.WriteLine(er); //docs.microsoft.com
Настоящий шаблон regex представляет собой мешанину из текста с параметрами и то, что показано в концепте в настоящем regex выглядит примерно так:
^(?!-)[A-Za-z0-9-]+([\\-\\.]{1}[a-z0-9]+)*\\.[A-Za-z]{2,6}$"
Это ещё простой шаблон валидации доменов. Бывают сложнее.
Техническая реализация
Несмотря на долгий опыт в PHP, я выбрал C# как основной язык для этого проекта. В текущем состоянии .net на голову выше экосистемы PHP. Ты просто садишься, открываешь Visual Studio 2019 и всё как по масти работает. Банальный отлов ошибок при компиляции вырезает 90% глупых ошибок.
Библиотека работает на C# под .net 5.
Cценарий работы c библиотекой
Создаёте новый объект правил BasicRule и заполняете публичные свойства правила. Правила можно складывать в друг-друга как матрёшку, но дочерние правила перестают быть самостоятельными и свой результат отдают родительскому правилу;
Создаёте объект Regexoop и помещаете в конструктор свои правила;
Вызываете метод Input() для помещения исходного текста для поиска;
Вызываете один из трех режимов работы методов для поиска текста.
Rule test = new BasicRule()
{
Name = "root",
Pattern = "Hello",
Start = Rule.Direction.start
};
List res = new Regexoop.Regexoop(test).Input("Hello World").Find();
//res: Hello
Да, тут я тоже ловлю некоторый фейспалм из-за "new Regexoop.Regexoop", но в скором времени хочу это исправить. Однако же работает. ????
Принцип разбора текста
Исходный текст обрабатывается посимвольно согласно шаблону правил. Вот кусок кода текущего роутера для метода Find()
while(_input.IsComplete() == false) //тут думаю ясно что это исходный текст
{
_input.MoveCursor(1); //курсор передвинется только в том случае если был запрошен исходный текст
Rule.Status stepResult = _rule.Peek().ParseSymbol(_input);
...
}
Обработка правил является стеком вызовов правил. Вы спросите зачем, вот пример.
Rule test = new BasicRule()
{
Name = "root",
Pattern = "Hello {world} Hello {world}",
Start = Rule.Direction.start,
Variables = new List {
new BasicRule() {
Name = "world",
Pattern = "World"
}
}
};
List res = new Regexoop.Regexoop(test).Input("Hello World Hello World").Find();
//res: Hello World Hello World
Встречая команду {world}
при парсинге шаблона, правило помечает необходимость переадресации на другое правило и помещает в специально отведённое место объект с необходимым правилом, а роутер считывая необходимость переадресации, замораживает текущее исполнение вызова правила и помещает в стек вызовов новое правило. Когда новое правило завершит свою работу со статусом Complete
, результат дочернего правила будет присвоено родительскому правилу и родительское правило продолжит работу с места остановки. В общем простая работа стековой машины вызовов.
Также сейчас реализована рекурсия, но я не добавил ещё добавление дочерних правил при вызове рекурсии. Поэтому в текущем варианте нормальная работает рекурсии гарантирована только для правил без дочерних правил.
Можно много ещё чего написать, т.к. мне это интересно, ведь я автор этой штуки, но мне кажется это будет слишком избыточно для статьи. В документации можно увидеть больше примеров и планы на разработку.
Pet-проект это тоже работа
Несмотря на получаемое удовольствие от проекта. Я не могу сказать что это просто. Если на обычной работе программистом ты делаешь только свою часть и в 18:00 уходишь в закат, то здесь всё не так. Даже не представляю сколько времени данный проект
В данном проекте я директор, pr менеджер, pm, team leader, разработчик, тестировщик, системный администратор и сам себе кофе делаю.
Уже 3 недели я выкладываю исходники на Github и только после оформления как nuget пакет я увидел что кто-то качает библиотеку. Наверное боты. 100%. Поэтому без рекламы ваши проекты никому не интересны, даже бесплатно. Пилите караваны и не бойтесь что их кто-то украдёт.
Жду от сообщества критику и предложения по развитию библиотеки.
Комментарии (26)
Oskard
04.11.2021 01:04+4На эту же тему есть мультиязычный https://github.com/VerbalExpressions
amarao
04.11.2021 17:39Спасибо и вам, и автору поста, который этот вопрос поднял. Выглядит (Rust-версия) как ровно то, что я хотел бы. Адекватную семантическую разметку вместо байт-кода для робота.
Oskard
04.11.2021 18:59Должен предупредить, по результатам использования в продакшене выяснилось, что репозитории для разных языков могут содержать несколько разное число реализованных фич, поэтому нужно не бояться если чего-то не хватает дописывать это прямо к ним в код.
saboteur_kiev
04.11.2021 01:09+9"Хм, regex такой сложный. НУ ПОЧЕМУ?
Ну почему он всем кажется сложный?
Регексп ПРОСТОЙ! Нужно просто сесть, прочитать одну книжку по регекспам (1-2 вечера) и посидеть немного на стековерфлоу или unixexchange посматривая задачи по регекспам и парся их. ВСЕ. Все регекспы становятся достаточно простыми.
Можно не стать супергуру, но как минимум 90% регекспов вы сможете читать сходу, и 60% из них писать сами. Как минимум если говорить по PCRE.jMas
04.11.2021 02:45+1У меня это было: каждый раз на практике разбираешь новое выражение, читаешь доки по мере надобности, пишешь свои выражения для конкретных кейсов. Проходит несколько лет и ты незаметно для себя владеешь регулярками и не представляешь как жил раньше.
По поводу статьи: автор просто учится, думаю пет-проект просто поможет разобраться, но а ещё полезно все таки реальные примеры из жизни.
sshikov
04.11.2021 13:06Книжку и 1-2 вечера даже много, если не пытаться конечно освоить все и сразу (а так делать вообще вредно). Ведь в принципе, базовые вещи совсем простые, и их достаточно мало.
Ведь по сути, то что автору кажется сложным, надо просто порезать на куски, и все станет понятным:
^(?!-)[A-Za-z0-9-]+([\\-\\.]{1}[a-z0-9]+)*\\.[A-Za-z]{2,6}$
^...$ — начало и конец строки, значит мы ищем совпадение не внутри строки, а со строкой целиком. Теперь про это забываем, и рассматриваем то что осталось по частям.
[A-Za-z0-9-] — латинские большие и маленькие буквы и цифры, и минус
[a-z0-9] — тоже самое маленькие буквы и цифры
[A-Za-z] — большие и маленькие буквы, без цифр на этот раз
Во всех случаях — одна штука
[]+ — одно или более повторений того что в скобках
{2,6} — от 2 до 6 повторений
{1} — ровно одно повторение
([\\-\\.]{1}[a-z0-9]+)* — а это комбинация из уже изученных вещей, как и вся регулярка в целом. Тут из нового круглые скобки, чтобы + или * применялись к группе как целому
Ну и что тут сложного, если совсем немного подумать? После этого от регулярки остались рожки да ножки — можно посмотреть в справочник, и понять, что означают остальные конструкции. А по сути, мы изучили всего несколько базовых вещей: *, +, {} как повторение, [] как диапазон символов, () как группировка, ^ И $. Ну и \\ еще.
И что я забыл, из нужного все время?saboteur_kiev
06.11.2021 01:08И что я забыл, из нужного все время?
Я регулярно использую lookahead и look behind, а также группы, чтобы потом можно было использовать back referenсe на сматченную группу.
sshikov
06.11.2021 09:37Я про каждый день. А это все же продвинутые фичи.
Ну и главное — пока я уложился в одну страницу. То есть правил-то совсем немного.saboteur_kiev
09.11.2021 19:41Так я тоже про каждый день.
при помощи lookahead и lookbehind можно грепом вырезать не просто нужные строчки, а еще и нужные столбцы или значения из другого мусора. Одной командой.
SadOcean
04.11.2021 15:02Просто речь о разных типах сложности.
Правила регекспа просты и емки.
Это и их преимущество и недостаток.
Недостаток в том, что за счет высокой плотности правила получаются write only - их крайне тяжело читать и менять впоследствии, особенно если не делаешь это регулярно.В моем случае это приводит к том, что правила то Я знаю, но сталкиваюсь так не часто и разнообразно (то в пайтоне, то в баше, то в шарповом коде), что каждый раз приходится мучительно вспоминать, как там начать строку, какой значок для букв и цифр и т.д.
Для моего случая verbal expression могут быть крайне удобен (да и на выходе он дает те же регекспы)
eternum
07.11.2021 18:43Он не сложный, он read only. Я не понимаю, кому вообще могла прийти идея в половине случаев экранировать обычные символы, а в половине - управляющие. Но даже если и пришла, для этого нужен был ещё один человек, который сказал "Мне норм. В продакшен".
Не поймите неправильно, в случае работы и регэспы парсятся и по логам дебажится, но всё же.netch80
08.11.2021 10:14> Он не сложный, он read only.
Не абсолютно. Но достаточно часто в реале приходится расставлять какие-то метки (в редакторе, на бумаге), чтобы нарисовать для себя структуру выражения.
О, идея: научить этому IDE. Пусть даже в отдельном окошке, но чтобы рисовал структуру, пояснял и делал фолдинг, где надо. Или уже есть плагин? Или все уже продумали и откинули эту идею?
Про сборку из частей (своими литералами, PEG, ещё как-то) я уже писал.
> Я не понимаю, кому вообще могла прийти идея в половине случаев экранировать обычные символы, а в половине — управляющие.
Исторически. Экранировка управляющих это специфика basic syntax, а не extended syntax, который сейчас в основном развивается всеми движками. А в basic с самого начала выделили небольшой набор символов для меты (.*^$ — и всё?), а дальше развивали через \.
На самом деле, если бы в базовых сделали _все_ меты через \, было бы системнее (и проще развивать). Но уже как получилось, так получилось — ломать миллионы мест уже никто не будет.
> Но даже если и пришла, для этого нужен был ещё один человек, который сказал «Мне норм. В продакшен».
Если посмотрите историю Unix, там таких промежуточных «мне норм, в продакшен», а через месяц «ой не то, надо править» было несколько штук в неделю, какие-то мелочи могли шлифоваться с десятками переделок. В то время была поговорка «типичный пользователь Unix не знает, как на этой неделе вызывается вывод на принтер».
Через несколько лет это всё обтесалось до приемлемого состояния.
QtRoS
04.11.2021 08:51У Alex Aiken есть курс Compilers. В самом начале курса так фундаментально разбираются возможности конечных автоматов и соответствующих им регулярных выражений, что после изучения их уже нельзя забыть/не понимать/сторониться. Очень рекомендую.
extempl
04.11.2021 09:27Погодите, но регулярные выражения были созданы чтоб простыню кода по парсингу скомкать в компактную строку. Её нет никакого смысла раскрывать обратно, вместо этого вы могли бы взять аналогичную простыню кода и попробовать её упростить до, например, упомянутого выше, https://github.com/sprache/Sprache.
leschenko
Очередная попытка убить регулярные выражения очередным велосипедом, который и 1% от их возможностей не покрывает и ничуть не делает код проще.
Не проще ли разобраться с регулярными выражениями, чтобы они вас больше не пугали?
fiftin
Все мы писали свои велосипеды, это часть обучения.
TyVik
Главное не тащить их потом в прод.
K1aidy
Согласен, посыл статьи не в том, что "гляньте, какого я убийцу фейсбука запилил", а в том, чтобы не бояться быть опозоренным, показывая свои начинания.
spaceatmoon Автор
Да, именно так. Возможно даже самая глупая идея понравится людям настолько, что кто-то сделает также. Замечу, что только после написания этой статьи, я понял некоторые ошибки.
netch80
Регэкспы в таком виде действительно тяжело читать. "/x" и "/xx" перлового движка придуманы для того, чтобы можно было описать каждую деталь в комментариях.
В движках типа Nitra используется PEG для лексического анализа, что позволяет писать с заданием имён конкретным элементам выражения, комментариями, и возможностей в сумме таки больше, чем у самых выдающихся движков вроде того же перлового.
То, что именно АС сделал движок с меньшими возможностями, показывает, что он не в курсе достижений индустрии — факт, но и само по себе намерение что-то сделать в этом — похвально.
amarao
Меня пугают регулярные выражения. Не смотря на то, что я их знаю и использую. Основная причина - регэксп - это код для state machine, подобие ассемблера. Там ничего не для человека. Это write only код, который можно глазами реверс-инженерить и только. Там нет ни имён, ни мест для синтакисческого ритма (отступы, семантические блоки).
Человек, который написал длинный и суровый регэксп - справился. А как насчёт того, что будет в этом регэкспе багу ловить?
netch80
> Там нет ни имён, ни мест для синтакисческого ритма (отступы, семантические блоки).
Если есть в языке возможность эффективно компилировать из строковых выражений (в большинстве промышленных языков это как-то обеспечивается), то такое можно обеспечить самому.
Я вот по другой причине (собственно иерархия синтаксиса), но строил в питоновском коде:
ну и так далее. Для парсера они предкомпилируется, и то, что у конкретного выражения может быть в итоге 1467 символов (исходник регэкспа для name-address) — уже неважно, структурно всё понятно, тестируется по кускам и так далее.
PS: Ну да, дальше предлагают механизмы, которые всё это оформляют уже в виде функций/методов — что-то вроде классического parser combinators. Если они могут потом скомпилировать результат во что-то эффективное (один автомат, как у классических регулярок) — отлично. Просто на одну зависимость больше :)
perevedko
Мне кажется, написать свои регулярки — хороший способ разобраться с "классическими".