12 февраля 2012 г.

Регулярные выражения в Haskell

Предыстория.
Где-то на отрезке времени длиной в пол года я экспериментировал с использованием регулярных выражений в Haskell. Потому что хотел использовать для написание небольших утилит, где как раз требовалось применение регулярных выражений. В конечном итоге сделал все на Ruby.

Вступление.
И как оказалось в Haskell нет единой библиотеки для поддержки регулярных выражений. Так обилие выбора создала проблему с выбором. Да и документации по каждой из библиотеке оказалось не так и много. Что и подтолкнула меня к написанию этого поста.
Примечания:
1) Я не буду приводить примеры типа "Hello world": поиск количества совпадение, захват фрагмента. В место этого приведу готовый листинг кода с основыными замечанными особенностями.
2) Под скальпель попали:
  • regex-pcre (0.94.2)
  • pcre-light (0.4)
  • regex-posix (0.95.1)
3) На производительность библиотек я не смотрел. Так как это было для меня вторичным.
То на что я обращал внимание:
- корректная обработка unicode и кириллицы (как оказалось это актуально!)
- возможность обработки строк (замена, разбитие на фрагменты)
4) Мои выводы могут быть неправильными, так как у меня не было подробной документации/примеров кода по каждой библиотеке.
Акт 1. На сцене regex-pcre
-- Пример  на тестирование Regex.PCRE
import qualified Text.Regex.PCRE as PCRE
{-
regex-pcre  v 0.94.2
"+" 
1) вроде как бы поддерживает PCRE нотацию
"-"
1) С некоторыми русскими буквам не работает ('Ф', 'Ъ' и др.)
2) Возможности для обработки строк (замена, разбитие) не нашел
-}
main = do
    putStrLn "START:"
    -- работает
    putStrLn "#0:"
    print $ testPCRE "Name: ([А-Яа-я\\s]+) Ser" "Name: ЧьетоИмя Ser" 
    -- работает
    putStrLn "#1:"
    print $ testPCRE "имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "имя:\r\nФЫВв \r\nфамилия" 
    -- работает
    putStrLn "#2:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфамилия" "Имя:\r\nФЫВв \r\nфамилия" 
    -- работает
    putStrLn "#3:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nфАмилия" "Имя:\r\nФЫВв \r\nфАмилия" 
    -- не работает (Не любит Regex.Posix букву "Ф")
    putStrLn "#4:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\nФамилия" "Имя:\r\nФЫВв \r\nФамилия" 
    -- Заменяем "Ф" на "[Ф]" и работает:
    putStrLn "#5:"
    print $ testPCRE "Имя:\r\n([А-Яа-я\\s]+)\r\n[Ф]амилия" "Имя:\r\nФЫВв \r\nФамилия" 
    
    print $ let regex = "имя:(.*?)фамилия" ; match = "имя:\r\nПетр \r\nфамилия" PCRE.=~ regex :: [[String]] in match
    print $ let m= "xyz abc more text" PCRE.=~ "(\\w+) \\w+" ::[[String]] in m
    putStrLn "#6 Поиск email: "
    -- Тут все нормально
    print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb" 
    -- А вот здесь текст захватвается паттерном!
    print $ testPCRE "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "регистрация"


testPCRE regex text = let match = text PCRE.=~ regex :: [[String]] in match


Оранжевым подсвечены ошибки.
Как оказалось самая худшая из всех тестируемых:
  • В документации были намеки на то что имеется возможность "настройки" поиска (нечувствительность регистра, точка - включает себя символ конца строки и т. п.) - но как это сделать не понятно. 
  •  Явная проблема с кириллицей. Были случаи ложного захвата в тексте на русском.
Акт 2. На сцене pcre-light
-- Пример  на тестирование Regex.PCRE.Light
import Text.Regex.PCRE.Light
import qualified Data.ByteString.UTF8 as B
{-
pcre-light v 0.4
"+":
1)шаблон регулярных выражений c PCRE нотацией
2)unicode и как следствие адекватная работа с кириллицей
"-"
1) Нет возможности работы с строками (замена, разбитие)
-}
main = do
    putStrLn $ B.toString  $ B.fromString "START:"
    print $ let r = compile (B.fromString "(\\s[фыва]+\\s)") [] in (match r (B.fromString "aaddd фыва dd") [])
    print $ testPCRELight "([\\w.\\-\\d]+@[\\w.\\-\\d]+)" "aaaa \r\nlesprom.spb@bk.ru\r\n bbbb" 
    print $ testPCRELight "\\b([\\w.\\-\\d]+@[\\w.\\-\\d]+)\\b" "регистрация"

testPCRELight :: String -> String -> Maybe [B.ByteString]
testPCRELight pattern str = let r = compile (B.fromString pattern) [] in (match r (B.fromString str) [])

-- Могут пригодится:
func1 :: Maybe [String] -> String
func1 (Just x) = last x
func1 Nothing = " - "

func2 :: Maybe [B.ByteString] -> B.ByteString
func2 (Just x) = last x
func2 Nothing = B.fromString " - "

Хорошая PCRE библиотека:
  1. PCRE и кириллица в норме.
  2. Нет возможности работы с строками (замена, разбитие). 
Акт 3. На сцене Нет возможности работы с строками (замена, разбитие)
-- Пример  на тестирование Regex.posix
import Text.Regex.Posix
import Text.Regex --Regular expression matching. Uses the POSIX regular expression interface in Text.Regex.Posix.
-- <) http://hackage.haskell.org/packages/archive/regex-compat/0.95.1/doc/html/Text-Regex.html
{-
regex-posix v. 0.95.1
Особенности:
1) Это Posix а не Perl нотация регулярных выражений,  по этому:
- нет никаких \w, \d, \s символьных классов
- нет ленивых квантификаторов (.*?), только жадные
2) Есть возможность замены в строковых переменных по posix регулярным выражениям
-}
main = do
    putStrLn $ "START:"
    putStrLn $ "exp #0:"
    print $ testCountMatches "12, 34, 78" 
    print $ testReplace "12 moon"
    putStrLn $ "exp #1:"
    print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday " "(/\\*.*\\*/)"
    putStrLn $ "exp #2:" -- Posix видимо использует только жадные квантификаторы
    print $ removeByPattern " /*Hel\r\nl122o*/ 13 friday /* ddd*/ dddd" "/\\*.*?\\*/"
    
testCountMatches :: String -> Int
testCountMatches inPut = inPut =~ "([0-9]+)" :: Int

testReplace :: String -> String
testReplace input = subRegex (mkRegex "([0-9]+)") input "White"

-- mkRegexWithOpts() parameters:
-- False -> meen that '.' include also "\n" character!
-- True -> meen that Case sensitive!
removeByPattern :: String -> String -> String
removeByPattern input pattern = subRegex (mkRegexWithOpts pattern False True) input ""

Все хорошо только PCRE все же не хватает.
Имеется возможность работы замены в строке, разбитие строк.

Заключение. 
Все течет. Все меняется. Надеюсь, с библиотеками так же.

Комментариев нет:

Отправить комментарий