Tenho um analisador que se parece com isto:
module Parser2 where
import Text.Parsec
import Text.Parsec.String (Parser)
import Text.Parsec.Language (emptyDef)
import qualified Text.Parsec.Expr as Ex
import qualified Text.Parsec.Token as Tok
data Expr
= Map [(Expr, Expr)]
| Int Integer
| Str String
deriving (Eq, Ord, Show)
lexer :: Tok.TokenParser ()
lexer = Tok.makeTokenParser style
where
ops = ["=>"]
names = []
style = emptyDef {
Tok.commentLine = "#"
, Tok.reservedOpNames = ops
, Tok.reservedNames = names
}
integer :: Parser Integer
integer = Tok.integer lexer
commaSep :: Parser a -> Parser [a]
commaSep = Tok.commaSep lexer
reserved :: String -> Parser ()
reserved = Tok.reserved lexer
int :: Parser Expr
int = Int <$> integer
maplit :: Parser Expr
maplit = do
reserved "{"
c <- commaSep $ do
k <- expr
reserved "=>"
v <- expr
return (k, v)
reserved "}"
return $ Map c
expr :: Parser Expr
expr = Ex.buildExpressionParser [] factor
stringLit :: Parser Expr
stringLit = Str <$> Tok.stringLiteral lexer
factor :: Parser Expr
factor = try stringLit
<|> try maplit
<|> try int
contents :: Parser a -> Parser a
contents p = do
Tok.whiteSpace lexer
r <- p
eof
return r
parseExpr :: String -> Either ParseError Expr
parseExpr = parse (contents expr) "<stdin>"
Isso pode analisar corretamente "mapas" com espaços em branco ao redor de inteiros, mas não sem. Para strings, isso não parece importar:
ghci> parseExpr "{\"a\"=> 1,\"b\"=> 2}"
Right (Map [(Str "a",Int 1),(Str "b",Int 2)])
ghci> parseExpr "{\"b\"=>\"c\"}"
Right (Map [(Str "b",Str "c")])
ghci> parseExpr "{\"a\"=>1}"
Left "<stdin>" (line 1, column 8):
unexpected '1'
expecting end of "=>"
Percebo que provavelmente há uma resposta óbvia relacionada aos makeTokenParser
padrões, mas não está claro para mim por que isso acontece e ainda não descobri como depurar corretamente.
Qualquer ajuda é bem-vinda.
O
reserved
analisador é destinado a analisar palavras-chave especificamente, então ele verifica se a palavra-chave não é usada como um prefixo para um identificador: em uma linguagem ondelet
é reservado, você quer analisarlettuce
como um identificador, não como a palavra-chavelet
seguida pelo identificadortuce
. Quais caracteres são permitidos em identificadores são controlados peloidentLetter
campo deGenLanguageDef
, que no caso deemptyDef
inclui dígitos, então seu=>1
não analisa como o reservado=>
seguido por1
since1
poderia ser parte de um identificador.Para analisar operadores como
=>
, você deve usarreservedOp
instead ofreserved
, que implementa a mesma lógica, mas para operadores em vez de identificadores, ou apenassymbol
se você não se importar com a verificação de prefixo.