регулярные выражения в C/C++

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

регулярные выражения в C/C++

Непрочитанное сообщение Olej » 06 сен 2016, 11:45

Это тема переплыла вот отсюда - локализация строк в C-коде
- как работать с регулярными выражениями в C++ и, особенно, C?
- какие там предоставляются возможности?
- как там в регулярных выражениях обстоят дела с локализованными (русскими) строками?
Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 00:34

Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Вар.№1

Код: Выделить всё

#include <stdio.h>
#define INIT      char *sp = instring;
#define GETC()    (*sp++)
#define PEEKC()   (*sp)
#define UNGETC(c) (--sp)
#define RETURN(c) return c;
#define ERROR(c)  printf( "error %d\n", c );
#include <regexp.h>

#define SIZE 80
int main( int argc, char *argv[] ) {
   char pattern[ SIZE ] = "s",
        buf[ SIZE ] = "test";
   if( !compile( *argv, pattern, &pattern[ SIZE ], '\0' ) ) {
      return 1;
   }
   if( !step( buf, pattern ) )
      printf( "no match\n" );
   else {
      for( char* p = loc1; p < loc2; p++ )
         printf( "%c", *p );
      printf( "\n" );
   }
   return 0;
}
Всё это построено на макроопределениях, и в описаниях сказано:
У этого файла неприятный интерфейс. Программы, которые включают данный файл, перед оператором #include <regexp.h> должны содержать определение пяти макросов, перечисленных ниже. Макросы используются функцией compile.
Вот уж действительно "неприятный"... :-(

Этот код компилируется без ошибок, но с такими вот характерными предупреждениями:

Код: Выделить всё

$ gcc -Wall regex0.c -o regex0
In file included from regex0.c:9:0:
/usr/include/regexp.h:31:2: предупреждение: #warning "<regexp.h> will be removed in the next release of the GNU C Library." [-Wcpp]
 #warning "<regexp.h> will be removed in the next release of the GNU C Library."
  ^
/usr/include/regexp.h:32:2: предупреждение: #warning "Please update your code to use <regex.h> instead (no trailing 'p')." [-Wcpp]
 #warning "Please update your code to use <regex.h> instead (no trailing 'p')."
  ^
Объяснения мы находим в комментариях файла <regexp.h>:
The contents of this header file were standardized in the Single Unix Specification, Version 2 (1997) but marked as LEGACY; new applications were already being encouraged to use <regex.h> instead. POSIX.1-2001 removed this header.
Это устаревший механизм, пришедший из OS Solaris, и сохранившийся только из соображений синтаксической совместимости. Он представляет только исторический интерес, на случай если вы столкнётесь с очень старым кодом.

Мне так и не удалось добиться работоспособности откомпилированного этого кода. Возможно, его поддержка уже действительно удалена из GNU библиотеки, как они и обещали. Возможно, я проявил недостаточно настойчивости.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 00:36

Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Вар.№2

Код: Выделить всё

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <locale.h>
#define __USE_GNU
#include <regex.h>

int main( int argc, char *argv[] ) {
#define SIZE 80
   char buf[ SIZE ] = "123,4567,898709",
        pattern[ SIZE ] = "^([^,]*),([^,]*),([^,]*)$";
   if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );      
   struct re_pattern_buffer *weight;
   weight = (struct re_pattern_buffer*)malloc( sizeof( struct re_pattern_buffer ) );
   if( !weight ) {
      printf( "allocate error %m\n" );    
      return 1;
   }
   re_set_syntax( RE_BACKSLASH_ESCAPE_IN_LISTS | RE_CHAR_CLASSES |
                  RE_NO_BK_BRACES | RE_NO_BK_PARENS | RE_NO_BK_VBAR | RE_INTERVALS );
   const char *err = re_compile_pattern( pattern, strlen( pattern ), weight );
   if( err ) {
      printf( "compile error: %s\n", err );
      free( weight );
      return 2;
   }
   struct re_registers regs;
   memset( &regs, 0, sizeof( regs ) );
   while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
      if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
      if( 0 == strlen( buf ) ) continue;
      printf( "'%s' ->\n", buf ); 
      int p = re_match( weight, buf, strlen( buf ), 0, &regs );
      if( p <= 0 ) { 
         printf( "no match\n" );
         continue;
      }
      for( int c = 0; ( c < p ) && ( regs.start[ c ] >= 0 ); c++ ) {
         printf( "%d/%d : ", regs.start[ c ], regs.end[ c ] );
         for( int i = regs.start[ c ]; i < regs.end[ c ]; i++ )
            printf( "%c", buf[ i ] );
         printf( "\n" );
      }
   }
   free( weight );
   return 0;
} 
Здесь оператор free() никогда не достигается, но в реальном коде после завершения сопоставления с образцом это должно делаться именно так.

Код: Выделить всё

$ ./regex1 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
^C

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 00:41

Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Вар.№3
(это то же, что и №2 - <regex.h>, но в POSIX нотации)

Код: Выделить всё

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <regex.h>

int main( int argc, char *argv[] ) {
#define SIZE 80
   char buf[ SIZE ], errbuf[ SIZE ] = "",
        pattern[ SIZE ] = "[0-9]";      
#define MATCHSIZE 20
   regmatch_t regs[ MATCHSIZE ];
   regex_t re, *pre = &re;
   if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );      
   int err = regcomp( pre, pattern, REG_ICASE | REG_EXTENDED ); 
   if( err ) {
       regerror( err, pre, errbuf, SIZE );
       printf( "%s\n", errbuf );    
       return 1;
   }
   while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
      if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
      if( 0 == strlen( buf ) ) continue;
      if( argc > 1 ) printf( "'%s' ->\n", buf ); 
      if( REG_NOMATCH == regexec( pre, buf, MATCHSIZE, regs, 0 ) ) {
         printf( "no match\n" );
         continue;
      }
      for( int c = 0; regs[ c ].rm_so != -1; c++ ) {
         printf( "%d/%d : ", regs[ c ].rm_so, regs[ c ].rm_eo );
         for( int i = regs[ c ].rm_so; i < regs[ c ].rm_eo; i++ ) 
            printf( "%c", buf[ i ] );
         printf( "\n" );    
      }
   }
   regfree( pre );
   return 0;
}
Здесь, как и в предыдущем варианте, нет необходимости (как это всегда обязательно при работе с широкими локализованными строками wchar_t[]) устанавливать соответствующую языковую локаль:

Код: Выделить всё

   setlocale( LC_ALL, "" );
Поиск и сравнения производятся не в терминах осмысленных символов иностранного языка, а в терминах обессмысленных байт в последовательностях UTF-8 представлений:

Код: Выделить всё

$ ./regex2 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
^C

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 00:42

Результат сопоставления (в обоих вариантах и показанных далее) представляется как массив совпадений (пусть и выраженных по-разному), 1-й элемент которого описывает общее соответствие, а последующие — это совпадения последовательных отмеченных подвыражений (\1, \2, \3, … в терминологии регулярных выражений):

Код: Выделить всё

$ ./regex1 "([0-9]*).([0-9]*).([0-9]*).([0-9]*)"
192.168.1.3
'192.168.1.3' ->
0/11 : 192.168.1.3
0/3 : 192
4/7 : 168
8/9 : 1
10/11 : 3
^C
Но такое (побайтное) сопоставление имеет побочные эффекты и требует хорошего понимания происходящего: можно искать в строках совпадений многобайтных последовательностей подстрок, но нельзя корректно выполнять всё, что формулируется в терминологии символов, трактуемых как единичные байты:

Код: Выделить всё

$ ./regex2 "Ё."
яываэё Ёйвап
'яываэё Ёйвап' ->
13/16 : Ё�
Конец сопоставленной последовательности в данном случае должен был бы завершаться на 17-м байте, а не 16-м.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 00:52

Olej писал(а): Дело в том, что из нескольких вариантов реализации регулярных выражений в C, ни один не предусматривает работы с широкими локализованными символами wchar_t.
Вар.№4
Это широко используемый пакет (его библиотеки) — PCRE (Perl Compatible Regular Expressions). Для этого вы должны иметь установленным соответствующий пакет (или установить его из репозитория своего дистрибутива):

Код: Выделить всё

$ dnf list pcre
Последняя проверка окончания срока действия метаданных: 0:00:15 назад, Thu Sep  8 19:12:56 2016.
Установленные пакеты
pcre.i686                  8.39-2.fc23                         @updates
pcre.x86_64                8.39-2.fc23                         @updates
Для DEB дистрибутивов тоже не проблема найти и установить требуемые библиотеки (от дистрибутива к дистрибутиву могут варьироваться только наименования пакетов репозиториев), для примера, это в Mint 17.2:

Код: Выделить всё

$ apt show libpcre3
Пакет: libpcre3
...
Сопровождающий: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
...
Описание: поддержка регулярных выражений, совместимых с Perl5 (динамическая версия)
 Библиотека предоставляет функции для работы с регулярными выражениями.
 Синтаксис и семантика выражений сделаны максимально похожими на регулярные
 выражения языка Perl 5. 
Используя этот инструментарий приложение, подобное предыдущим, может быть сделано так:

Код: Выделить всё

#include <stdio.h>
#include <string.h>
#include <locale.h>
#include <pcre.h>

int main( int argc, char *argv[] ) {
#define SIZE 80
   char buf[ SIZE ] = "test test test test ", // errbuf[ SIZE ] = "",
        pattern[ SIZE ] = "s";      
#define MATCHSIZE 20
   const unsigned char *locale_tables = pcre_maketables();
   if( argc > 1 ) strncpy( pattern, argv[ 1 ], SIZE - 1 );
   int options = 0;
   const char *error;
   int erroffset;
   pcre *re = pcre_compile( (char*)pattern, options, &error, &erroffset, locale_tables );
   if( !re ) {
      printf( "pattern error: %s\n", error );
      return 1;
   }
   int count = 0;
   int ovector[ MATCHSIZE * 2 ];
   while( fgets( buf, sizeof( buf ) - 1, stdin ) ) {
      if( buf[ strlen( buf ) - 1 ] == '\n') buf[ strlen( buf ) - 1 ] = '\0';
      if( 0 == strlen( buf ) ) continue;
      if( argc > 1 ) printf( "'%s' ->\n", buf ); 
      count  =  pcre_exec( re, NULL, (char*)buf, strlen( buf ), 0, 0, ovector, MATCHSIZE );
      if( count < 0 ) {
         printf( "no match\n" );
         continue;
      }
      for( int c = 0; ( c < 2 * count ) && ( ovector[ c ] >= 0 ); c += 2 ) {
         printf( "%d/%d : ", ovector[ c ], ovector[ c + 1 ] );
         for( int i = ovector[ c ]; i < ovector[ c + 1 ]; i++ ) 
            printf( "%c", buf[ i ] );
         printf( "\n" );    
      }
   }      
   return 0;
}
Здесь та же история, что и ранее: сначала скомпилировать шаблон регулярного выражения, а затем сколько угодно раз сопоставлять его с входными строками. Глубоко в недрах документации библиотеки утверждается, что если последний параметр вызова компиляции pcre_compile() равен NULL, то используется собственной таблицей символов pcre_default_tables, которая определена в исходном файле chartables.c и вкомпилирована в модуль библиотеки. Но эту таблицу можно изменить вызовом pcre_maketables(), которая не предусматривает параметров и использует текущую установленную локаль.

Для сборки обязательна библиотека (и в ряде дистрибутивов её лучше указать в командной строке после файла исходного кода … на самом деле — после объектного файла):

Код: Выделить всё

$ gcc -Wall regex3.c -l pcre -o regex3
Проверяем то, что из этого получилось:

Код: Выделить всё

$ ./regex3 "Ё"
это Ёлка
'это Ёлка' ->
7/9 : Ё
а это ёлка
'а это ёлка' ->
no match
31.12.2016 NewYear - Ёлка 
'31.12.2016 NewYear - Ёлка' ->
21/23 : Ё
^C
(Литеры 'Ё' и 'ё', если помните, имеют в Unicode особое соотношение с русским алфавитом, а поэтому заслуживают особой проверки … как и 'Й' и 'й'.)

Код: Выделить всё

$ ./regex3 "^([^,]*),([^,]*),([^,]*)$"
123,4567,898709
'123,4567,898709' ->
0/15 : 123,4567,898709
0/3 : 123
4/8 : 4567
9/15 : 898709
a,b,c
'a,b,c' ->
0/5 : a,b,c
0/1 : a
2/3 : b
4/5 : c
й,ё,Ё
'й,ё,Ё' ->
0/8 : й,ё,Ё
0/2 : й
3/5 : ё
6/8 : Ё
слово,ещё слово,снова слово
'слово,ещё слово,снова слово' ->
0/50 : слово,ещё слово,снова слово
0/10 : слово
11/28 : ещё слово
29/50 : снова слово
слово , word, ещё слово
'слово , word, ещё слово' ->
0/36 : слово , word, ещё слово
0/11 : слово
12/17 :  word
18/36 :  ещё слово
^C

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 09 сен 2016, 23:11

Естественно, все перечисленные выше инструменты применимы и в коде на языке C++ (поскольку язык C++ является надмножеством языка C, а не каким-то совершенно новым языком). Хотя библиотека PCRE (см. выше) гораздо чаще обсуждается и применяется относительно C++, чем C (так было до стандарта C++11).
Тем не менее, C++ предлагает свои инструменты работы с регулярными выражениями — определения в файле <regex>, работающие по честному с строками локализованных (широких) символов wsttring. Но использовать эти возможности можно только с компилятором (или его режимами), поддерживающим, как минимум, стандарт C++11. В противном случае, вы сразу же получите сообщение об ошибке:

Код: Выделить всё

$ g++ regex1++.cc -oregex1++
In file included from /usr/include/c++/5.3.1/regex:35:0,
                 from regex1++.cc:3:
/usr/include/c++/5.3.1/bits/c++0x_warning.h:32:2: ошибка: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.
Приложение, сделанное во многом эквивалентным показанным раньше (в C):

Код: Выделить всё

#include <iostream>
#include <locale>
#include <regex>
using namespace std;

int main( int argc, char *argv[] ) {
   locale::global( locale ( "ru_RU.utf8" ) );
   wstring buf = L"Строка строка строка";
   wstring wp = L"строка";
   if( argc > 1 ) {
      wchar_t warg[ strlen( argv[ 1 ] ) + 1 ];
      mbstowcs( warg, argv[ 1 ], strlen( argv[ 1 ] ) + 1 );
      wp = wstring( warg );
   }
   basic_regex<wchar_t> pattern( wp );     // образец      
   wcmatch match;                          // результат сопоставления
   while( wcin >> buf ) {
      wcout << L'\'' << buf << L"' ->" << endl;
      if( !regex_search( (wchar_t*)buf.c_str(), match, pattern ) ) {
         wcout << "no match" << endl;
         continue;
      }
      for( auto &m: match ) wcout << ": " <<m << endl;
   }
}
Теперь всё стало гораздо короче. И это понитно — поддержка <regex> сделана через STL, в частности, результат сопоставлений wcmatch — это vector<wstring>. В этом и оборотная сторона этого инструмента: при ошибках в определениях типов в вашем коде создаются целые простыни сообщений об ошибках, которые почти невозможно истолковывать.
Как и следовало ожидать, такой код совершенно корректно работает с локализованными текстами:

Код: Выделить всё

$ g++ -Wall -std=c++11 regex1++.cc -o regex1++
$ ./regex1++ "^([^,]*),([^,]*),([^,]*)"
слово1,слово2,слово3,слово4
'слово1,слово2,слово3,слово4' ->
: слово1,слово2,слово3
: слово1
: слово2
: слово3
^C
Интересно заглянуть в заголовки <regex>. В частности, на вариации стиля сопоставления:

Код: Выделить всё

      static constexpr flag_type icase = regex_constants::icase;
      static constexpr flag_type nosubs = regex_constants::nosubs;
      static constexpr flag_type optimize = regex_constants::optimize;
      static constexpr flag_type collate = regex_constants::collate;
      static constexpr flag_type ECMAScript = regex_constants::ECMAScript;
      static constexpr flag_type basic = regex_constants::basic;
      static constexpr flag_type extended = regex_constants::extended;
      static constexpr flag_type awk = regex_constants::awk;
      static constexpr flag_type grep = regex_constants::grep;
      static constexpr flag_type egrep = regex_constants::egrep;
Это показывает и сколько различающихся диалектов синтаксиса регулярных выражений размножилось в природе, когда один и тот же образец будет восприниматься по-разному, давая различный результат. В коде своего сопоставления вы можете определить это флагами при создании шаблона, например так:

Код: Выделить всё

   basic_regex<wchar_t> pattern( wp, regex_constants::icase | regex_constants::awk );
(Здесь, заодно, показана установка флага нечувствительности к регистру текста, по умолчанию шаблон различает регистр.)

P.S. В приложении - все сразу показанные до сих пор примеры.
Вложения
regex_04.odt
(43.76 КБ) 137 скачиваний
Regex_04.tgz
(6.26 КБ) 147 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 10 сен 2016, 21:19

Olej писал(а):P.S. В приложении - все сразу показанные до сих пор примеры.
Дальнейшие редакции этого текста (дополнения, изменения, исправления, ...) см. здесь: Регулярные выражения C/C++

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 12 сен 2016, 22:32

Olej писал(а):
- как работать с регулярными выражениями в C++ и, особенно, C?
- какие там предоставляются возможности?
Книги (переводные), из числа самых приличных, по регулярным выражениям ... для читателей начиная с самых начинающих.
Ссылки для свободного скачивания:

Изображение
Скачать: Friedl J. / Фридл Дж. - Mastering Regular Expressions / Регулярные выражения (3-е издание), 2008г., СПб "Символ-Плюс", ISBN: 5-93286-121-5, 608 страниц

Изображение
Скачать: Ян Гойвертс, Стивен Левитан, Регулярные выражения. Сборник рецептов, 2-е издание, 2015г., СПб "Символ-Плюс", ISBN: 978-5-93286-221-6, 704 страницы

Изображение
Скачать: Майкл Фицджеральд, Регулярные выражения. Основы, 2015г., "Вильямс", ISBN: 978-5-8459-1953-3, 144 страниц

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: регулярные выражения в C/C++

Непрочитанное сообщение Olej » 13 сен 2016, 11:26

Здесь вот онлайновый тестер регулярных выражений, который позволяет проверить результат сопоставления, без написания какого-либо программного кода и не используя никакие GNU утилиты: Regex Pal. :!:
Очень удобно при отработке.

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 4 гостя