24 dias de Hackage, 2015 - dia 14 - Earley: Uma biblioteca de parsers promisora para Haskell
por Franklin Chen
traduzido por Pedro Yamada
Esse é um artigo escrito por Franklin Chen e traduzido para o português. Ler original.
Índice de toda a série
O índice de toda a série está no topo do artigo para o dia 1.
Encontro HaskellBR São Paulo
Dia 14
No
dia 10,
mostrei como usar s-expressions para evitar ter de escrever um parser
customizado. Mas escrever parsers não é nada mal em Haskell; ou será que é?
A popular biblioteca parsec
tem muitos problemas,
porque ela requere backtracking hackeado à mão que causa mensagens de erro
estranhas e dificuldade de pensar sobre a sua gramática. Há um fork melhorado
do parsec
chamado
megaparsec
,
mas ainda é o mesmo tipo de tecnologia. Que tal algo completamente diferente?
O recente pacote Earley
é
intrigante e eu comecei a o usar para novos projetos onde eu não preciso do
poder monádico de algo como o parsec
mas me contento com uma API de
applicatives e não preciso da performance de algo como o attoparsec
. Além
de boas mensagens de erro, ele lida bem com parsing inline e ambiguidades.
Hoje vou dar dois exemplos pequenos de usar o Earley
.
Instalação
Já que o LTS Stackage está um pouco atrasado agora, e o Earley
não para de
mudar, decidi usar a última versão do Earley
modificando nosso stack.yaml
:
Parsing para a AST do dia 10
Vamos voltar para o problema de diferenciação simbólica do dia 10 e criar uma sintaxe infixa de tipo matemático para “parsear”.
Testes
Aqui estão alguns testes do HSpec/QuickCheck para ilustrar o que nós queremos
quando “parseamos” uma Exp
de uma string.
Imports
Text.Earley
é o módulo principal do pacote Earley
; Report
é usado para
retornar um relátorio do progresso do “parse”.
Testes do QuickCheck
Alguns testes do QuickCheck que mostram que expressões como
x*a + y*b * (z+c)
“parseiam” para a AST esperada:
Erros de parse esperados
Finalmente, um exemplo de como checar por erros de parse esperados. Os tokens de erro são definidos pelo o usuário e adicionados à construção de gramáticas, como veremos.
Implementação
A implementação envolve idiomas de Applicative
que vão ser familiares se você
já usou o parsec
.
Imports
O operador <?>
é usado para adicionar uma expectativa (que nós decidimos
especificar como uma string, com o sinônimo de tipo Expected
) para um
production.
Drivers
O que queremos para nosso problema em particular é um parser que recebe uma string como input e espera que possa fazer parse completo dela. Construimos ele a partir de um parser mais genérico que vem de processar nossa gramática.
Gramática
Nossa gramática é bem direta. Earley
usa uma mônada para manter seu estado
interno e nós usamos a extensão do GHC RecursiveDo
(coberta em um
dia de extensões do GHC de 2014)
para conseguirmos nos referir para uma regra da gramática recursivamente. Note
que a recursão da esquerda para a direita não tem problemas no Earley
.
Prod
é o
construtor de tipo para uma production
e você constrói productions usando combinadores como satisfy
e symbol
.
Para mais exemplos de gramáticas veja o
diretório de exemplos no repositório do GitHub do Earley
.
(Update of 2015-12-17)
O inverso de parsing é pretty-printing, coberto no dia 17.
N.T. Não traduzido; link vai para o original
Só por diversão: resolvendo o problema “number words”
A habilidade de lidar com ambiguidades e retornar todos os parses possível é algo útil em muitas situações. Aqui, mostro uma solução para o problema “number word”. No passado, eu lidei com ambiguidades usando o suporte GLR do Happy, mas não gosto de escrever parsers com ele.
O problema “number word”:
Dado um inteiro positivo, retorne todas as formas que um inteiro pode ser representado por letras usando o mapa
1 -> A, 2 -> B, ..., 26 -> Z
. Por exemplo, o número 1234 pode ser representado pelas palavrasABCD
,AWD
eLCD
.
Essa é uma versão de brincadeira de um problema sério, a segmentação em linguagens naturais.
O teste
O teste reflete o enunciado do problema:
Note que estou usando uma lista NonEmpty
de Char
s porque uma string vazia
não é uma solução válida para o problema. (Cobri NonEmpty
no
dia 7).
Solução
A solução é só escrever uma gramática que tente escolher dígitos consecutivos
para formar uma letra. Nós criamos uma production para cada letra que nos
importa, usando o numberLetterFor
, combinamos todas elas por meio de
alternação usando asum
e conseguimos uma production composta, que é a
gramática.
Conclusão
Só recentemente descobri a biblioteca de parsers Earley
e comecei a
usá-la. Eu estou animado com o quão amigável é.
Todo o código
Todo o código para a série estará nesse repositório do GitHub.
Nota do tradutor
Se você quer ajudar com esse tipo de coisa, agora é a hora. Entre no
Slack ou no
IRC da HaskellBR e
contribua. Esse blog e outros projetos associados estão na
organização haskellbr
no GitHub e em
haskellbr.com/git.