Как правильно проанализировать блок с отступом с помощью мегапарсек?

#haskell #megaparsec

#haskell #мегапарсек

Вопрос:

Я пытаюсь создать язык программирования, основанный на отступах, и я пытаюсь проанализировать что-то вроде:

 expr1 :
  expr2
  expr3
  

Здесь, по сути : , указывает на начало нового блока с отступом, поэтому expr1 совершенно не имеет значения, идея заключается в том, что : он может появиться в любом месте строки и должен быть последним символом строки.

Я получил этот код, который более или менее работает:

 block :: Parser Value
block = dbg "block" $ do
  void $ symbol ":"
  void $ eol
  space1
  (L.indentBlock spaceConsumer indentedBlock)
  where
    indentedBlock = do
      e <- expr
      pure (L.IndentMany Nothing (exprs -> pure $ Block () (e : exprs)) expr)
  

Но проблема в том, что в примере только первое выражение блока анализируется с правильным отступом, остальные должны иметь больший отступ, например

 expr1 :
  expr2
   expr3
   expr4
   expr5
  

Ответ №1:

Я не могу дать конкретный совет по megaparsec, поскольку я не знаю этой конкретной библиотеки, однако я могу поделиться с вами своей мудростью, полученной при написании нескольких языковых анализаторов, чувствительных к отступам: ваша жизнь будет намного проще, если вы будете лексировать и анализировать отдельными шагами и добавлять indent_begin и indent_end во время лексикографического анализа.

Ответ №2:

Обычно я добавляю следующие комбинаторы:

 import qualified Text.Megaparsec.Char.Lexer as L

indented :: Pos -> Parser a -> Parser (Pos, a)
indented ref p = do pos <- L.indentGuard space GT ref 
                    v <- p
                    pure (pos, v)
        

aligned :: Pos -> Parser a -> Parser a
aligned ref p = L.indentGuard space EQ ref *> p
  

Затем вы можете использовать L.indentLevel для получения ссылочного отступа.

Вот пример синтаксического анализа блока инструкций, включая обработку ошибок:

 blocked1 :: Pos -> Parser a -> Parser [a]
blocked1 ref p = do (pos, a) <- indented ref p
                    rest <- many (try $ helper pos)
                    fpos <- getPosition
                    rest' <- traverse (reportErrors pos) rest
                    setPosition fpos
                    pure (a : rest')
    where helper pos' = do pos <- getPosition
                           a <- p
                           when (sourceColumn pos <= ref) $ L.incorrectIndent EQ pos' (sourceColumn pos)
                           pure (pos, a)
          reportErrors ref (pos, v) = setPosition pos *>
            if ref /= sourceColumn pos
               then L.incorrectIndent EQ ref (sourceColumn pos)
               else pure v
                
blocked :: Pos -> Parser a -> Parser [a]
blocked ref p = blocked1 ref p <|> pure []

block :: Pos -> Parser (Block ParserAst)
block ref = do
       s <- blocked1 ref stmt
       pure $ Block s


funcDef :: Parser (FuncDef ParserAst)
funcDef = annotate $
    do pos <- L.indentLevel 
       symbol "def"
       h <- header
       l <- localDefs 
       b <- block pos
       pure $ FuncDef h l b
  

Ответ №3:

Я закончил разбор expr1 прямо в том же месте, что и :

По-видимому indentBlock , отсчет начинается со столбца, с которого начинается синтаксический анализатор, переданный в качестве последнего параметра, поэтому идея состоит в том, чтобы начать синтаксический анализ с начала строки (относительно текущего уровня отступа), в итоге получилось так:

 block :: Parser Value
block =
  L.indentBlock spaceConsumer indentedBlock
  where
    indentedBlock = do
      caller <- callerExpression
      args <- parseApplicationArgs
      pure (L.IndentSome Nothing (exprsToAppBlock caller args) parse)
    exprsToAppBlock caller args exprs =
      pure (Application () caller (args <> [Block () exprs]))