如何使用 megaparsec 正确解析缩进块?
How to properly parse indented block with megaparsec?
我正在尝试制作一种基于缩进的编程语言,我正在尝试解析如下内容:
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
我无法提供 megaparsec 具体建议,因为我不知道那个特定的库,但是我可以通过编写一些缩进敏感语言解析器来给你我的智慧:如果你将 lex 和解析分开,你的生活会容易得多步骤并在词典分析过程中添加 indent_begin
和 indent_end
。
我通常添加以下组合器:
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
我最终在与 :
相同的位置解析了 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]))
我正在尝试制作一种基于缩进的编程语言,我正在尝试解析如下内容:
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
我无法提供 megaparsec 具体建议,因为我不知道那个特定的库,但是我可以通过编写一些缩进敏感语言解析器来给你我的智慧:如果你将 lex 和解析分开,你的生活会容易得多步骤并在词典分析过程中添加 indent_begin
和 indent_end
。
我通常添加以下组合器:
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
我最终在与 :
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]))