Escrevendo parsers com Haskell
marcelocamargo@linuxmail.org
github.com/haskellcamargo
λ. O que é um parser
λ. O que é um parser
λ. 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 ]
[ ; ]
λ. 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;
λ. O que é um parser ─ quebrando o front-end de um compilador
var x = 10 + 20 * 30;
____ = ____
/ \
x ___ + ___
/ \
10 __ * __
/ \
20 30
λ. O que é um parser ─ projetando linguagens
λ. Parser generators × parser combinators ─ parser generators
λ. Parser generators × parser combinators ─ parser generators
λ. Parser generators × parser combinators ─ parser generators
λ. Parser generators × parser combinators ─ parser combinators
λ. Parser generators × parser combinators ─ parser combinators
λ. Parser generators × parser combinators ─ parser combinators
λ. Parser generators × parser combinators
λ. Que tal o Parsec?
λ. Que tal o Parsec?
cabal install parsec
λ. Que tal o Parsec?
stack install parsec
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
slides.com/marcelocamargo/parsec