#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]))