Back-End

26 jan, 2012

Trabalhando com expressões regulares em C++11

Publicidade

O código deste artigo está disponível no GitHub: https://github.com/sol-prog/regex_tutorial.

Agora que o C++11 é um padrão publicado e a maior parte dos principais compiladores já possui algum padrão C++11 implementado, é hora de aprender ou reaprender o que praticamente se tornou uma nova linguagem. Como eu exploro alguns dos recursos mais novos do padrão, planejo escrever sobre meu progresso e esperançosamente ajudar outras pessoas a começarem a aprender C++11.

Começarei com uma série de artigos nos quais apresentarei um recurso da linguagem por vez, ilustrado com exemplos completos e funcionais. Cada artigo terá também um curto parágrafo sobre em quais compiladores e sistemas operacionais eu testei meu código. Eu testarei cada exemplo no Linux (g++-4.6.1 e clang), Mac OSX (g++-4.6.1 e  clang) e finalmente Windows (Visual C++ 2010 e g++-4.6.1).

Validar a entrada de dados do usuário sempre foi um problema no C++, especialmente quando o usuário tem que inserir um número. Cada programador era forçado a escrever seu próprio verificador de erros para checar se os dados de entrada eram válidos, basicamente não era uma sintaxe uniforme para verificar a veracidade dos dados do usuário.  Entre com C++11 e expressões regulares! Agora é possível verificar de uma maneira simples e eficaz se o input é o que você, como programador, esperava do usuário.

O C++11 tem suporte para algumas gramáticas de expressões regulares, como ECMAScript, awk, grep e algumas outras. Todos os exemplos neste artigo usam a sintaxe ECMAScript. Para poder usar as capacidades do regex  C++11, você precisará saber (ou estar disposto a aprender) a gramática do ECMAScript.

Vamos começar com um exemplo simples, digamos que estamos tentando validar um inteiro inserido pelo usuário. O C++ irá aceitar este tipo de input numérico: 0012, 12, +0012 ou o negativo -0012, -12, -0012. Quaisquer zeros à esquerda serão ignorados, e para números positivos nós podemos usar com segurança, ou não, o sinal de mais. 

Primeiramente, precisamos de uma expressão regular que irá corresponder ao formato dos números acima. De acordo com o ECMAScript, um dígito é identificado com [:digit:] ou with [:d:]. Uma expressão regular que irá corresponder a qualquer número de um dígito é (usando a sintaxe C++11):

1 regex r("[[:digit:]]");

O acima irá corresponder a qualquer input da forma 0 .. 9. Se você quiser corresponder um número maior que um dígito (uma string de dígitos), você irá usar o sinal de mais no final do regex acima:

1 regex r("[[:digit:]]+");

Mas e um número com sinais como -12 ou +12? Com o sinal de menos, podemos simplesmente testar se a primeira posição no input é menos ou não. Isso pode ser conseguindo usando  -? em frente da nossa expressão regular, e teremos:

1 regex r("-?[[:digit:]]+");

O sinal de mais é um caractere especial na sintaxe ECMAScript, então você precisa informar ao compilador para tratar o sinal de mais como um caractere + e não como um operador de repetição, isso pode ser feito ao “escapar” o caractere especial com \, no entanto, \ também é um caractere especial, então acabamos escrevendo:

1 regex r("\\+?[[:digit:]]+");

Agora, tudo que temos que fazer é combinar as duas últimas expressões, basicamente um número pode somente ter um sinal de menos ou de mais, então precisamos usar o operador “or”, que para o ECMAScrip é o caractere |. Então agruparemos a expressão “+ ou -” usando um par de parênteses:

1 regex r("(\\+|-)?[[:digit:]]+");

Vamos envolver a expressão acima em programa simples que irá pedir por um integer até que o usuário insira “q”. Se tivermos uma correspondência entre nosso regex e o input, imprimiremos “inteiro”:

//Example 1
#include <iostream>
#include <regex>
#include <string>

using namespace std;

int main()
{
string input;
regex integer("(\\+|-)?[[:digit:]]+");
//As long as the input is correct ask for another number
while(true)
{
cout<<"Give me an integer!"<<endl;
cin>>input;
//Exit when the user inputs q
if(input=="q")
break;
if(regex_match(input,integer))
cout<<"integer"<<endl;
else
{
cout<<"Invalid input"<<endl;
}
}
}

Salve o código acima em um arquivo chamado “regex_01.cpp”. Para compilar o código acima em uma máquina Mac OSX com o Xcode 4, usaremos:

1 clang++ -std=c++0x -stdlib=libc++ regex_01.cpp -o regex_01.out

Rodando o código acima:

No momento em que escrevo este artigo, o gcc não tem suporte para o regex, os únicos compiladores que são capazes de compilar o código acima (do meu conhecimento) são clang e Visual C++ 2010.

E se testarmos com um número real? Primeiro vamos construir um regex que irá corresponder somente a estes tipos de formato: -x, -x., -x.xx, e assim por diante. Podemos usar como ponto de partida a expressão regular para inteiro. Para a parte fracionária de um número real, nós obviamente iremos usar uma expressão parecida sem a parte do sinal: [[:digit:]]+.

Um número real pode ser inserido sem a parte fracionária, precisamos marcar a parte fracionária como “opcional” na nossa expressão, isso pode ser escrito como ([[:digit:]]+)?. Além disso, o separador decimal tem que ser opcional, uma expressão regular para um número real pode ser escrita assim:

1 regex rr("((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?");

Você pode encontrar um exemplo completo com o regex acima em  “regex_02.cpp” no github.

Vamos construir uma expressão regular que pode ser usada para corresponder números escritos em formato científico, p.e. -1.23e+06, 0.245e10, 1E5. Vamos começar com estas observações: a parte exponencial é opcional, o sinal também é opcional. A primeira parte da nossa expressão regular obviamente será a que foi usada anteriormente. Para a parte exponencial usaremos ((e|E)((\\+|-)?)[[:digit:]]+)? :

1 regex rr("((\\+|-)?[[:digit:]]+)(\\.(([[:digit:]]+)?))?((e|E)((\\+|-)?)[[:digit:]]+)?");

Um exemplo com o regex acima está no github, o nome do arquivo é “regex_03.cpp”.

Expressões parecidas podem ser construídas para testar qualquer tipo de dado do usuário. Se você quiser aprender mais sobre expressões regulares, a fonte oficial do campo é o livro Mastering Regular Expressions, de Jeffrey E.F. Friedl:

Para aqueles interessados em aprender a nova sintaxe do C++11, eu recomendaria ler o Professional C++,  de M. Gregoire, N. A. Solter 3 S. J. Kleper.

?

Texto original disponível em http://solarianprogrammer.com/2011/10/12/cpp-11-regex-tutorial/