Parsec
Escrevendo parsers com Haskell
marcelocamargo@linuxmail.org
github.com/haskellcamargo
About.me
- Systems analyst
- Translator
- PL researcher
- Developer of Quack and Capybara programming languages
TOC
λ. O que é um parser?
λ. Que tal o Parsec?
λ. Parser combinators × parser generators
λ. Referências & recomendações
O que é
um
parser?
do latim...
Pars (ōrātiōnis)
Parte (de uma linguagem)
λ. O que é um parser
-
Base de um compilador
-
Análise sintática baseada em uma gramática
-
Construção de linguagens
λ. O que é um parser
LEXER
λ. O que é um parser ─ quebrando o front-end de um compilador
var x = 10 + 20 * 30;
[ var]
[IDENTIFIER x ]
[ = ]
[INTEGER 10 ]
[ + ]
[INTEGER 20 ]
[ * ]
[INTEGER 30 ]
[ ; ]
- Divide o texto em um conjunto de tokens
- Classifica-os com tags e talvez valores
- “Alimenta” o parser
PARSER
λ. O que é um parser ─ quebrando o front-end de um compilador
var x = 10 + 20 * 30;
var-decl := "var" IDENTIFIER "=" expression;
expression := expression ("+"|"*") expression
| INTEGER;
- Consome tokens
- Possui uma gramática
- Pode gerar uma árvore sintática
ÁRVORE SINTÁTICA
λ. O que é um parser ─ quebrando o front-end de um compilador
var x = 10 + 20 * 30;
____ = ____
/ \
x ___ + ___
/ \
10 __ * __
/ \
20 30
- Árvore representa o programa
- Análise semântica
- Pode gerar código
GRAMÁTICA FORMAL
λ. O que é um parser ─ projetando linguagens
- Definição formal de uma linguagem
- Conjunto de regras e produções que a validam
Parser generators
Parser combinators
×
O que é
um parser
generator?
O que é um parser generator?
λ. Parser generators × parser combinators ─ parser generators
- Ferramenta que recebe uma linguagem formal e dá o código fonte de um parser como saída
- Grande parte das linguagens modernas são escritas usando parser generators
Exemplos de parser generators
λ. Parser generators × parser combinators ─ parser generators
- ANTLR (Groovy, Jython)
- Bison (Ruby, PHP, Go, Bash, MySQL)
Bison grammar for PHP integers
λ. Parser generators × parser combinators ─ parser generators
O que é
um parser
combinator?
O que é um parser combinator?
λ. Parser generators × parser combinators ─ parser combinators
- Função de mais alta ordem que aceita vários parsers de entrada e dá outro como saída
- Parsers são compostos sempre através de outros parsers, tudo de maneira combinada
O que é um parser combinator?
λ. Parser generators × parser combinators ─ parser combinators
- Parser combinators são legíveis, modulares, bem estruturados e fáceis de manter
- Não são dependentes de um lexer, em sua maioria
Exemplos de parser combinators
λ. Parser generators × parser combinators ─ parser combinators
- Parsec (for Haskell)
- Parsimmon (for JavaScript)
Parser generators rodam compile-time
λ. Parser generators × parser combinators
Parser combinators rodam com o seu programa
Que tal
o
Parsec?
Parsec é um
parser
combinator!
Que tal o Parsec?
λ. Que tal o Parsec?
- Poderoso combinador monádico de parsers para Haskell
- Analisa gramáticas sensíveis ao contexto, finitas ou infinitas
Que tal o Parsec?
λ. Que tal o Parsec?
- Combine parsers e crie expressões maiores
- Existem ports para JS, Python, OCaml, Java, F# e mais uma dezena de linguagens
cabal install parsec
λ. Que tal o Parsec?
stack install parsec
Escrevendo
um parser de
JSON
module JSON where
{-# LANGUAGE NoMonomorphismRestriction #-}
-- Precisamos para mostrar o JSON
import Data.List (foldl', intersperse)
-- Analisar sequências de escape
import Data.Char (chr, ord)
-- Representar arrays associativos
import qualified Data.Map as Map
import Text.Parsec
import Text.Parsec.Char
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Import stuff...
-- Nossa estrutura para representar o JSON
data JSON = JsonNum Double
| JsonStr String
| JsonArr [JSON]
| JsonAA (Map.Map String JSON)
| JsonBool Bool
| JsonNull
deriving (Eq, Ord)
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Representamos a gramática usando ADTs
-- Transformar um JSON em uma string para exibição
instance Show JSON where
showsPrec _ = showsJSON where
commaJoin =
foldl' (.) id . intersperse (", " ++)
showsAssoc (field, val) =
shows field . (": " ++) . shows val
showsJSON (JsonNum num) =
shows num
showsJSON (JsonStr str) =
shows str
showsJSON (JsonArr jarr) =
('[' :) .
commaJoin (map shows jarr) .
(']' :)
showsJSON (JsonAA aa) =
('{' :) .
commaJoin (map showsAssoc $ Map.toList aa) .
('}' :)
showsJSON (JsonBool tf) =
if tf then ("true" ++) else ("false" ++)
showsJSON JsonNull =
("null" ++)
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Criamos uma função para exibição do JSON
-- A base do parser
jsonParser = do
spaces -- coma os espaços iniciais
-- Analise as possibilidades
json <- jsonPNum
<|> jsonPStr
<|> jsonPBool
<|> jsonPNull
<|> jsonPArr
<|> jsonPAA
<?> "value" -- esperava...
spaces
return json
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Definimos as possibilidades de valores
jsonPNull = try (string "null")
>> return JsonNull
λ. Que tal o Parsec? ─ Construindo um parser de JSON
JSON pode conter valores nulos, certo?
jsonPBool = fmap JsonBool
$ (try (string "true") >> return True)
<|> (try (string "false") >> return False)
λ. Que tal o Parsec? ─ Construindo um parser de JSON
E booleanos!
jsonPNum = do
-- Uma função que pode negar nosso resultado
doNeg <- (char '-' >> return negate) <|> return id
-- Todo número pode...
realPart <- realP
fracPart <- fracP
expPart <- expP
return $ JsonNum $ doNeg $ (realPart + fracPart) * 10 ** expPart
-- Parte real do número. Deve existir
where realP = (char '0' >> return 0) <|> (do
-- 0 ou entre 1 a 9
firstDigit <- satisfy (\ ch -> '1' <= ch && ch <= '9')
-- Depois, quaisquer digitos
restDigits <- many digit
return $ read $ firstDigit : restDigits
)
fracP = (do
char '.'
digits <- many1 digit
return $ read $ "0." ++ digits
)
-- Se não há um (.), o padrão é 0.
<|> return 0
expP = (do
oneOf "eE"
-- É opcional a notação exponencial
doNeg <- (char '-' >> return negate)
<|> (char '+' >> return id)
<|> return id
digits <- many1 digit
return $ doNeg $ read digits
)
-- 0 é padrão para a notação exponencial
<|> return 0
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Números são um pouco mais complexos...
jsonStr = do
char '\"'
strChar `manyTill` (char '\"')
-- Lista de substituição após \
where subMap = Map.fromList
[ ('\"', '\"')
, ('\\', '\\')
, ('/', '/')
, ('n', '\n')
, ('r', '\r')
, ('f', '\f')
, ('t', '\t')
, ('b', '\b')
]
-- Análise unicode
unicode = do
-- 4 dígitos hexadecimais
code <- count 4 hexDigit
return $ chr $ read $ "0x" ++ code
-- Finalmente analisar o \
strChar = (do
char '\\'
ch <- anyChar
-- Buscar caso de substituição
case Map.lookup ch subMap of
Just newCh -> return newCh
Nothing -> do
if ch == 'u' -- It might yet be unicode
then unicode
else fail "expecting escape sequence"
)
-- Não há sequência? Retorne o próprio caractere
<|> anyChar
-- Guarde o valor do parsing numa string JSON
jsonPStr = fmap JsonStr jsonStr
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Podemos representar strings com escape
-- Somente listas de valores JSON separados por
-- vírgulas!
jsonPArr = do
char '['
jarr <- jsonParser `sepBy` (char ',')
char ']'
return $ JsonArr $ jarr
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Que tal representarmos arrays simples?
jsonPAA = do
char '{'
jAA <- assocP `sepBy` char ','
char '}'
return $ JsonAA $ Map.fromList $ jAA
where assocP = do
spaces
label <- jsonStr
spaces
char ':'
json <- jsonParser
return (label, json)
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Arrays associativos são tão simples quanto!
-- Podemos até fazer um mini-REPL!
main = do
line <- getLine
case parse jsonParser "" line of
Right json -> do
print json
main
Left err -> print err
λ. Que tal o Parsec? ─ Construindo um parser de JSON
Sério? Um parser de JSON em 100 linhas?
E como usamos?
Referências
&
Recomendações
λ. Referências & recomendações
slides.com/marcelocamargo/parsec
- Compilers: principles, techniques and tools
- Let's build a compiler!
OBRIGADO!
Questions or Concerns?
Parsec: Escrevendo parsers com Haskell
By Marcelo Camargo
Parsec: Escrevendo parsers com Haskell
- 1,822