haskell parsec

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.