haskell - Parsec: diferencia entre “try” y “lookAhead”?
(1)
¿Cuál es la diferencia entre las funciones "try" y "lookAhead" en parsec?
Los combinadores try
y lookAhead
son similares en cuanto a que ambos permiten que Parsec "retroceda", pero se aplican en diferentes circunstancias. En particular, try
rebobinar el error mientras que lookAhead
rebobina el éxito.
En la documentación, try
"pretende que no ha consumido ninguna entrada cuando se produce un error", mientras que lookAhead p
"analiza p
sin consumir ninguna entrada", pero "si p
falla y consume alguna entrada, también lo hace lookAhead
".
Por lo tanto, si piensa que un analizador se ejecuta como caminar a lo largo de un estado de transmisión y falla o tiene éxito, lo que podríamos escribir en términos de Haskell como
type Parser a = [Tokens] -> (Either Error a, [Tokens])
a continuación, try
asegura de que si (try p) input ---> (Left err, output)
input == output
y lookAhead
tenga tal que (lookAhead p) input ---> (Right a, output)
luego input == output
, pero si (lookAhead p) input ---> (Left err, output)
entonces se puede permitir que difieran.
Podemos ver esto en acción mirando directamente el código de Parsec, que es algo más complejo que mi noción de Parser
anterior. Primero examinamos ParsecT
newtype ParsecT s u m a
= ParsecT {unParser :: forall b .
State s u
-> (a -> State s u -> ParseError -> m b) -- consumed ok
-> (ParseError -> m b) -- consumed err
-> (a -> State s u -> ParseError -> m b) -- empty ok
-> (ParseError -> m b) -- empty err
-> m b
}
ParsecT
es un tipo de datos basado en la continuación. Si nos fijamos en cómo se construye uno de ellos.
ParsecT $ /s cok cerr eok eerr -> ...
Verá cómo tenemos acceso a las funciones State su
, s
y cuatro que determinan cómo avanzamos. Por ejemplo, la cláusula de ParsecT
de la ParsecT
de ParsecT
de ParsecT
usa la opción eerr
, construyendo un ParseError
desde la posición de entrada actual y el mensaje de error pasado.
parserFail :: String -> ParsecT s u m a
parserFail msg
= ParsecT $ /s _ _ _ eerr ->
eerr $ newErrorMessage (Message msg) (statePos s)
Mientras que el análisis de token exitoso más primitivo ( tokenPrim
) usa una secuencia compleja de eventos que eventualmente culminan en llamar a cok
con un State su
actualizado State su
.
Con esta intuición, la fuente para try
es particularmente simple.
try :: ParsecT s u m a -> ParsecT s u m a
try p =
ParsecT $ /s cok _ eok eerr ->
unParser p s cok eerr eok eerr
Simplemente construye un nuevo ParsecT
basado en el pasado para intentarlo, pero con la continuación "empty err"
en lugar del error consumido. Cualquiera que sea el combinador de análisis que vea a continuación, try p
no podrá acceder a su continuación "consumed err"
real y, por lo tanto, try está protegido de cambiar su estado en caso de errores.
Pero lookAhead
es más sofisticado.
lookAhead :: (Stream s m t) => ParsecT s u m a -> ParsecT s u m a
lookAhead p = do{ state <- getParserState
; x <- p''
; setParserState state
; return x
}
where
p'' = ParsecT $ /s cok cerr eok eerr ->
unParser p s eok cerr eok eerr
Examinando solo el punto where
vemos que depende depende de la modificación del parser p
pasado para usar la continuación "empty ok"
en lugar de la continuación "consumed ok"
. Esto es simétrico a lo que hizo el try
. Además, garantiza que el estado del analizador do
vea afectado por lo que ocurra cuando esta p''
modificada se ejecute a través de su do
block.