Подтвердить что ты не робот

Parsec: разница между "try" и "lookAhead"?

В чем разница между функциями "try" и "lookAhead" в parsec?

4b9b3361

Ответ 1

Комбинаторы try и lookAhead аналогичны, поскольку они оба позволяют Parsec "перематывать", но они применяются в разных обстоятельствах. В частности, try перематывает сбой, а lookAhead перематывает успех.

В документации try "притворяется, что он не потреблял никаких входных данных при возникновении ошибки, тогда как lookAhead p" анализирует p, не потребляя никакого ввода ", но" if p терпит неудачу и потребляет некоторые input, то есть lookAhead ".

Итак, если вы думаете о том, что парсер работает как ходьба по некоторому потоковому состоянию и либо не работает, либо преуспевает, что мы можем записать в терминах Haskell как

type Parser a = [Tokens] -> (Either Error a, [Tokens])

то try гарантирует, что если (try p) input ---> (Left err, output), то input == output и lookAhead имеет такое значение, что (lookAhead p) input ---> (Right a, output) then input == output, но если (lookAhead p) input ---> (Left err, output), то им может быть разрешено различать.


Мы можем увидеть это в действии, посмотрев на код для Parsec напрямую, что несколько сложнее, чем мое понятие Parser выше. Сначала рассмотрим 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 - это тип данных, основанный на продолжении. Если вы посмотрите, как один из них построен

ParsecT $ \s cok cerr eok eerr -> ...

Вы увидите, как у нас есть доступ к State s u, s и четырем функциям, которые определяют, как мы продвигаемся вперед. Например, предложение fail экземпляра ParsecT Monad использует параметр eerr, создавая ParseError из текущей позиции ввода и переданного сообщения об ошибке.

parserFail :: String -> ParsecT s u m a
parserFail msg
    = ParsecT $ \s _ _ _ eerr ->
      eerr $ newErrorMessage (Message msg) (statePos s)

В то время как самый примитивный успешный анализ токена (tokenPrim) использует сложную последовательность событий, в конечном итоге кульминацию которой вызывает вызов cok с обновленным State s u.

С этой интуицией источник try особенно прост.

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

Он просто создает новый ParsecT на основе переданного для попытки, но с продолжением "empty err" вместо потребленной ошибки. Вне зависимости от того, компилятор синтаксического анализа видит, что try p не сможет получить доступ к его фактическому продолжению "consumed err", и, таким образом, попытка защищена от изменения его состояния при ошибках.

Но lookAhead более сложный

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

Изучая только where -clause, мы видим, что это зависит от модификации прошедшего парсера p, чтобы использовать продолжение "empty ok" вместо продолжения "consumed ok". Это симметрично тому, что сделал try. Кроме того, он гарантирует, что состояние парсера не зависит от того, что происходит, когда этот измененный p' запускается через его do -block.