суббота, 13 ноября 2010 г.

Linq-to-Regex

Думал про нестандартные использования Linq (НЕ Linq-to-<источник данных>). В процессе, исходя из логических умозаключений, набрал в гугле фразу "linq-to-regex". Раньше никогда про него не слышал, не знал, не видел, не думал, что оно такое есть...

Но Linq-to-Regex должен был существовать, по следующим причинам:
1. Регулярное выражение, особенно не банальное [A-Za-z0-9]+ - штука довольно сложная для визуального восприятия.
2. Regex'ы - это идеальное вместилище для труднопрогнозируемых ошибок из серии "очепятка". А шарп по своей идеологии категорически не должен такое разрешать.
3. Regex'ы как класс реализованы в C# и .Net отвратительно, совершенно не встроены в язык, поддерживают очень мало полезных фишек... И несмотря на то, что я обожаю перл, и обожаю перловые регексы, и нет, наверное, ни одного скрипта на перле, где бы я их не использовал - несмотря на всё это, за все те 3 года, которые я пишу на C#, я юзал шарповые Regex'ы ну может раз 5-6 (причем, чаще всего в валидаторах, и регексы чаще всего были крайне примитивными)...

Осознав это, и зная, что Linq действительно хорошо решает задачи упрощения кода, (вместе с тем делая его более строгим и типизированным, шарповым), смело набрал эту фразу в гугле. И не ошибся, попытка создать Linq-to-Regex действительно была. Полюбуйтесь примером:
[Test]
public void FindEmailUsingPattern()
{
    var query = from match in 
        RegexQuery.Against("sdlfjsfl43r3490r98*(*Email@somewhere.com_dakj3j")
                where match.Word.Repeat.AtLeast(1)
                    .Literal("@")
                    .Word.Repeat.AtLeast(1)
                    .Literal(".")
                    .Choice.Either(
                            Pattern.With.Literal("com"),
                            Pattern.With.Literal("net"))
                    .IsTrue()
                select match;
    foreach (var match in query)
    {
        Assert.AreEqual("Email@somewhere.com",match.Value);
    }
}
К сожалению, при ближайшем рассмотрении, выяснилось, что линка как такового тут почти и нет. Пожалуй, в данном случае мы имеем как раз с тем, что ребята из пальца высасывали эту связку. В большей степени мы тут видим просто некий fluent interface, и то, на мой взгляд, неудачный. Но здесь дело даже не в нём (на самом деле есть более удачный вариант). Дело в неверном подходе.

Мне кажется, авторы Linq-to-Regex слишком зацикливались на самом названии, "Linq-to-Regex". Не нужен в C# регекс как таковой. Нужен просто его адекватный заменитель. Стандартный Linq уже может оперировать строкой как IEnumerable<char>, но не позволяет многих фишек, на которые способен Regex.

Основные фишки регекса следующие:
 - определение паттерна текста из нескольких следующих друг за другом символов из выбранного диапазона
 - определение паттерна текста как некой последовательности статических символов
 - группировка паттернов и вытаскивание этих групп из исходной строки
 - развилки ("|")
 - всякие условия и флаги, типа совпадения начиная с начала строки

Я набросал примерчик, как это могло бы выглядеть:
// примерный аналог регекса 
//   [A-Za-z]+[A-Za-z0-9\.\-_]*@[A-Za-z]+[A-Za-z0-9\.\-_]*\.[A-Za-z]{2,4}
// должно вернуть "dkd.ru@man-r.na"

StringMatch[] matches = "ldskfj@dnfnf#dkd.ru@man-r.na,dlr"
    .StartMatch()
    .MatchChars(c => Char.IsLetter(ch), CharsCount.Once)
    .MatchChars(c => Char.IsLetterOrDigit(ch) || new char[] { '_', '.', '-' }.Contains(ch), 
                CharsCount.NeverOrMore)
    .MatchChars(c => c == "@", CharsCount.Once),
    .MatchChars(c => Char.IsLetter(ch), CharsCount.Once)
    .MatchChars(c => Char.IsLetterOrDigit(ch) || new char[] { '_', '.', '-' }.Contains(ch), 
                CharsCount.NeverOrMore)
    .MatchChars(c => c == ".", CharsCount.Once)
    .MatchChars(c => Char.IsLetter(ch), new CharMatchBounds(2, 4))
    .EndMatch();
На мой взгляд, подобным этому синтаксисом уже можно будет более-менее пользоваться... И неважно что писать долго, зато разобраться можно, ошибки легко отлавливаются, есть интеллисенс, magic symbols не используются и т.д.

Но то, что есть сейчас - это какой-то нелепый пришлепок, в самом деле... Не для шарпа.

1 комментарий:

Внимание! Реклама и прочий спам будут беспощадно удаляться.