Estoy trabajando en la separación de las etapas de lectura y análisis de un analizador. Después de algunas pruebas, me di cuenta de que los mensajes de error son menos útiles cuando uso tokens que no sean los tokens Char de Parsec.Haskell Parsec - los mensajes de error son menos útiles al usar tokens personalizados
Éstos son algunos ejemplos de mensajes de error de Parsec durante el uso de fichas de Char:
ghci> P.parseTest (string "asdf" >> spaces >> string "ok") "asdf wrong"
parse error at (line 1, column 7):
unexpected "w"
expecting space or "ok"
ghci> P.parseTest (choice [string "ok", string "nop"]) "wrong"
parse error at (line 1, column 1):
unexpected "w"
expecting "ok" or "nop"
Así, analizador de cadena muestra lo que se espera de cadena cuando se encuentra una cadena inesperada, y la elección analizador muestra cuáles son las alternativas.
Pero cuando se utiliza mismas combinadores con mis fichas:
ghci> Parser.parseTest ((tok $ Ide "asdf") >> (tok $ Ide "ok")) "asdf "
parse error at "test" (line 1, column 1):
unexpected end of input
En este caso, no se imprime lo que se esperaba.
ghci> Parser.parseTest (choice [tok $ Ide "ok", tok $ Ide "nop"]) "asdf "
parse error at (line 1, column 1):
unexpected (Ide "asdf","test" (line 1, column 1))
Y cuando uso choice
, no se imprime alternativas.
Espero que este comportamiento esté relacionado con las funciones del combinador, y no con tokens, pero parece que estoy equivocado. ¿Cómo puedo arreglar esto?
Aquí está el código léxico + analizador completo:
lexer:
module Lexer
(Token(..)
, TokenPos(..)
, tokenize
) where
import Text.ParserCombinators.Parsec hiding (token, tokens)
import Control.Applicative ((<*), (*>), (<$>), (<*>))
data Token = Ide String
| Number String
| Bool String
| LBrack
| RBrack
| LBrace
| RBrace
| Keyword String
deriving (Show, Eq)
type TokenPos = (Token, SourcePos)
ide :: Parser TokenPos
ide = do
pos <- getPosition
fc <- oneOf firstChar
r <- optionMaybe (many $ oneOf rest)
spaces
return $ flip (,) pos $ case r of
Nothing -> Ide [fc]
Just s -> Ide $ [fc] ++ s
where firstChar = ['A'..'Z'] ++ ['a'..'z'] ++ "_"
rest = firstChar ++ ['0'..'9']
parsePos p = (,) <$> p <*> getPosition
lbrack = parsePos $ char '[' >> return LBrack
rbrack = parsePos $ char ']' >> return RBrack
lbrace = parsePos $ char '{' >> return LBrace
rbrace = parsePos $ char '}' >> return RBrace
token = choice
[ ide
, lbrack
, rbrack
, lbrace
, rbrace
]
tokens = spaces *> many (token <* spaces)
tokenize :: SourceName -> String -> Either ParseError [TokenPos]
tokenize = runParser tokens()
Analizador:
module Parser where
import Text.Parsec as P
import Control.Monad.Identity
import Lexer
parseTest :: Show a => Parsec [TokenPos]() a -> String -> IO()
parseTest p s =
case tokenize "test" s of
Left e -> putStrLn $ show e
Right ts' -> P.parseTest p ts'
tok :: Token -> ParsecT [TokenPos]() Identity Token
tok t = token show snd test
where test (t', _) = case t == t' of
False -> Nothing
True -> Just t
SOLUCIÓN:
Ok, después de la respuesta de fp4me y la lectura fuente de Char de Parsec más cuidadosamente, terminé con esto:
{-# LANGUAGE FlexibleContexts #-}
module Parser where
import Text.Parsec as P
import Control.Monad.Identity
import Lexer
parseTest :: Show a => Parsec [TokenPos]() a -> String -> IO()
parseTest p s =
case tokenize "test" s of
Left e -> putStrLn $ show e
Right ts' -> P.parseTest p ts'
type Parser a = Parsec [TokenPos]() a
advance :: SourcePos -> t -> [TokenPos] -> SourcePos
advance _ _ ((_, pos) : _) = pos
advance pos _ [] = pos
satisfy :: (TokenPos -> Bool) -> Parser Token
satisfy f = tokenPrim show
advance
(\c -> if f c then Just (fst c) else Nothing)
tok :: Token -> ParsecT [TokenPos]() Identity Token
tok t = (Parser.satisfy $ (== t) . fst) <?> show t
Ahora que estoy recibiendo mismos mensajes de error:
ghci> Parser.parseTest (opción [tok $ Ide "ok", tok $ Ide "nop"]) "asdf"
de análisis error al (línea 1, columna 1):
inesperado ("asdf" Ide, "test" (línea 1, columna 3))
esperando Ide "ok" o Ide "nop"
Por qué ¿Quieres separar lexing del análisis? Seguramente la razón principal para hacer esto es la tradición: era más simple escribir un analizador complejo libre de los detalles de implementación del lexer (que era más rutinario, tal vez solo expresiones regulares), y en lenguajes imperativos, hace que sea más fácil separar el etapas.En un buen terreno de Haskell Parsec, escribir los lexers y los analizadores sintácticos es agradable y fácil: lee algunas cuerdas, combínalos para analizarlos, casi puedes escribir la definición de tu lenguaje en combinators. Además, estás trabajando duro para pasar posiciones; deja que Parsec lo haga. – AndrewC
@AndrewC, puede que tenga razón. Solo quería ver las partes buenas y malas de separar las etapas de lectura y análisis en parsec. Ahora, después de ver mi código final, creo que voy a usar solo el analizador. (también, una vez que estaba usando alex + happy para analizar una gramática basada en sangría y el lexing me ayudó a generar tokens sangría + dedent y a dejar que el analizador trabaje en gramática simplificada. La etapa lexing separada en parsec también podría ayudar en este tipo de situaciones) – sinan
@AndrewC, también, realmente me encanta Parsec y creo que poder trabajar en diferentes tipos de transmisiones (que no sean secuencias de caracteres) puede ser muy útil y escribir un lexer me ayudó a comprender cómo puedo hacerlo. Ahora sé cómo puedo trabajar en cadenas de bytes, por ejemplo. – sinan