Red – Parser

Introdução

É possível dizer que o objetivo do Parser no Red é o mesmo das Expressões Regulares (ER) em outras linguagens de programação. As diferenças básicas são:

  • Criado como uma dialeto (DSL) em Red.
  • Mais legível que uma ER (depende?).
  • Por ser criado em Red, possui uma grande integração com a linguagem.

O objetivo é, dada uma entrada qualquer (em Red é uma série como string, lista, etc.) e, baseando-se em determinadas regras, iremos verificar as ocorrências que casam na entrada para tomarmos diversas decisões. Em Red, podemos apenas verificar o casamento das regras retornando falso ou verdadeiro, coletar as partes onde a regra casa ou alterar os trechos onde as regras casam, etc..

Vejamos um caso prático e simples:

Desejamos verificar se o formato de um CPF é válido. Iremos aceitar valores no formato de três séries de três números, separados por ponto, um separador para o controle que poderá ser hífen ou barra e uma sequência de dois número para o controle. Ficaria algo como 999.999.999[-/]99. Uma das opções para o Parse em Red seria:

digitos: charset "0123456789"
centena: [3 digitos]
dezena: [2 digitos]
separador: charset "/-"
cpf: [centena dot centena dot centena separador dezena]

Diferente das ERs, Red não vem com a definição de dados numéricos (\d) para o Parser. Mas, como pode ser visto, é simples de definir. Também definimos centena como uma sequência de três dígitos e dezena como uma sequência de dois dígitos para facilitar a leitura da regra. Definimos os dois caracteres para o separador. O charset especifica que qualquer um dos caracteres irá casar com a regra. Poderíamos definir como [“/” | “-“], isto é, barra ou hífen. Se definíssemos apenas como “/-“, para casar, os dois caracteres deveriam estar presentes na ordem indicada. Alguns caracteres como dot, newline, cr, etc. já estão definidos na linguagem. Optei por usar o dot em vez de “.” para facilitar a leitura da regra. Finalmente criei a regra cpf (que é composta por várias outras regras) para validar se uma entrada é um CPF válido ou não (não estou calculando o controle, apenas o formato da entrada).

Se eu estiver no REPL do Red, uma sessão típica poderia ser:

red>> parse "100.010.001-00" cpf
== true

red>> parse "000.000.000/00" cpf
== true

red>> parse "999.111.555/77" cpf
== true

red>> parse "192.168.45" cpf
== false

red>> parse "192.168.45-77" cpf
== false

Outra possibilidade para a utilização da regra CPF seria para extrair todos os CPFs de um texto. Supondo o seguinte texto:

NOME: João
IP: 192.45.77.44
CPF: 111.111.111/11
NOME: Antônio
IP: 194.58.58.9
CPF: 123.456.789-00
NOME: Empresa Ltda.
IP: 201.195.45.44
CGC: 44.555.666/0001-11

No REPL e supondo-se que o texto estivesse sendo referenciado por s, teríamos algo como:

red>> parse s [collect some [keep cpf | skip]]
== ["111.111.111/11" "123.456.789-00"]

Aqui o collect irá colocar os CPFs da entrada em uma lista e o keep informa o que deverá ser armazenado na lista. No caso, keep cpf ou salta para a próxima posição na entrada.

Entre as outras utilizações para a regra, a possibilidade de padronizar o formato do CPF tipo trocar a barra pelo hífen.

Como falei antes “integrado com a linguagem Red”, é possível passar um código para a regra e ele será interpretado quando a regra é satisfeita. Por exemplo (considerando o texto acima):

parse s [some [copy v cpf (print ["Encontrei:" v]) | skip ]
Encontrei: 111.111.111/11
Encontrei: 123.456.789-00
== true

Quando a regra casar com a entrada, v conterá uma referência ao valor encontrado e o código que estiver entre parênteses será executado.

Sei que você não vai dizer, mas caso diga algo como: “Ah mas com ER eu escrevo menos. Seria algo como \d{3}\.\d{3}\.\d{3}-\d{2}”

Se o problema fosse só quantos caracteres você digita, certamente você iria programar em J ou K. Se você não programa nessas linguagens, você não está sendo 100% sincero. O ideal seria a gente pensar, pressionar enter e …. o programa se materializar no disco. Bem, também é possível diminuir a verbosidade no Red. Poderia ter algo como:

D: charset "0123456789"</pre>
<pre>cpf: [3 D dot 3 D dot 3 D "-" 2 D]

Não ficou tão ruim né? Pessoalmente, até acho um pouco mais legível que a ER para um leigo.

Por curiosidade, se você quiser mostrar o título de uma página html, o código ficaria assim:

page: read http://www.red-lang.org/
parse page [thru &lt;title&gt; copy titulo to &lt;/title&gt;]
print titulo

e será apresentado “Red Programming Language”.

Recursividade

As regras podem ser recursivas. Se você deseja validar se uma entrada é uma expressão matemática válida, você pode aplicar algo como o código abaixo. Pense em algo como BNF.

expr:    [term ["+" | "-"] expr | term]
term:    [factor ["*" | "/"] term | factor]
factor:  [primary "**" factor | primary]
primary: [some digit | "(" expr ")"]
digit:   charset "0123456789"

Para as regras acima você usou, basicamente, apenas o que já foi mostrado neste artigo. O único acréscimo foi de some que significa 1 ou mais ocorrências, isto é, no mínimo um dígito. Tem o any que significa zero ou mais. Se você conhece ER irá associar imediatamente os dois meta-caracteres para as duas funções.

Então parse “1+2*(3-2)/4” expr irá retornar true (é uma expressão válida para a regra) ao passo que parse “1-(3/)+2” expr irá retornar false (a expressão é inválida conforme a regra).

Então é isso. Ficar por aqui para não escrever um livro (a única forma de abranger detalhadamente tudo).

 

Anúncios