parsec:解析嵌套代码块

parsec: parse nested code blocks

我想解析以下文本:

keyword some more values
        funcKeyw funcName1
        funcKeyw funcName2

        funcKeyw funcName3

        keyword some more values
                 funcKeyw funcName2

        keyword some more values
                 funcKeyw funcName4

缩进由制表符完成。每个块都以 keyword 和同一行中的一些附加值开始。缩进的所有内容都属于同一个块。 所有函数调用(以funcKeyw关键字开头)之后,可以有子keyword块(由"empty"行分隔;"empty" 表示其中没有内容或空白字符)。

type IndentLevel = Int

data Block = Block { blockFuncCalls :: [String]
                   , blockBlocks    :: [Block]
                   }

block :: GenParser Char st Block
block = parseBlock 0
    where
        parseBlock lvl = do
            count lvl tab
            string "keyword"
            -- [...] Parse other stuff in that line.
            newline

            -- Parse 'function calls'.
            fs <- sepBy1 (blockFunc (lvl + 1)) emptyLines
            -- Parse optional child blocks.
            emptyLines
            bs <- sepBy (parseBlock (lvl + 1)) emptyLines

            return Block { blockFuncCalls=fs
                         , blockBlocks=bs
                         }

blockFunc :: IndentLevel -> GenParser Char st String
blockFunc lvl = do
    count lvl tab
    string "funcKeyw"
    -- [...] Parse function name etc..
    newline
    return funcName -- Parsed func name.

emptyLine :: GenParser Char st ()
emptyLine = many (oneOf "\t ") >> newline >> return ()

emptyLines :: GenParser Char st ()
emptyLines = many emptyLine >> return ()

问题是blockFunc解析器在子块开始时并没有停止解析,而是returns报错unexpected 'keyword'

我怎样才能避免这种情况?我想我可以使用 trychoice 为每一行选择正确的解析器,但我想要求函数调用在子块之前进行。

我注意到的一件事是 sepBy 组合器有一些意想不到的行为,即如果开始解析分隔符并且失败,整个 sepBy 失败,而不是简单返回到目前为止解析的内容。您可以使用以下变体,它们的区别在于 sepBy1Try 内的附加 try

sepBy1Try :: (Stream s m t) => ParsecT s u m a -> ParsecT s u m sep -> ParsecT s u m [a]
sepBy1Try p sep = do
  x <- p
  xs <- many (try $ sep *> p)
  return (x:xs)

sepByTry p sep = sepBy1Try p sep <|> return []

使用这些代替 sepBy

block :: GenParser Char st Block
block = parseBlock 0
    where
        parseBlock lvl = do
            count lvl tab
            string "keyword"

            otherStuff <- many (noneOf "\r\n") 
            newline

            -- Parse 'function calls'.
            fs <- sepBy1Try (blockFunc (lvl + 1)) emptyLines

            -- Parse optional child blocks.
            emptyLines
            bs <- sepByTry (try $ parseBlock (lvl + 1)) emptyLines

            return Block { blockFuncCalls=fs
                         , blockBlocks=bs
                         , blockValues=words otherStuff
                         }

我还修改了您的数据类型以捕获更多信息(仅用于演示目的)。另外,请注意递归 parseBlock 前面的另一个 try - 这是因为此解析必须在不消耗输入的情况下失败,例如当它看到一个选项卡但期望有两个时,这个 try 允许它回溯到 "next level"。

最后,更改以下内容:

emptyLines :: GenParser Char st ()
emptyLines = many (try emptyLine) >> return ()

这里的推理与 sepBy 相同...


为清楚起见,使用简单的漂亮打印机进行测试:

data Block = Block { blockValues :: [String]
                   , blockFuncCalls :: [String]
                   , blockBlocks    :: [Block]
                   } deriving (Show, Eq) 

pprBlock :: Block -> String 
pprBlock = unlines . go id where 
  go ii (Block vals funcs subblocks) = 
    let ii' = ii . ('\t':) in 
    (ii $ unwords $ "keyword":vals) : 
    map (\f -> ii' $ "function " ++ f) funcs ++ 
    concatMap (go ii') subblocks

test0_run = either (error.show) (putStrLn.pprBlock) $ parse block "" $ test0

test0 = unlines $ 
  [ "keyword some more values"
  , "\tfuncKeyw funcName1"
  , "\tfuncKeyw funcName2"
  , "\t"
  , "\tfuncKeyw funcName3"
  , "\t"
  , "\tkeyword some more values"
  , "\t\tfuncKeyw funcName2"
  , ""
  , "\tkeyword some more values"
  , "\t\tfuncKeyw funcName4"
  ]

>test0_run
keyword some more values
        function funcName1
        function funcName2
        function funcName3
        keyword some more values
                function funcName2
        keyword some more values
                function funcName4

>putStrLn test0
keyword some more values
        funcKeyw funcName1
        funcKeyw funcName2

        funcKeyw funcName3

        keyword some more values
                funcKeyw funcName2

        keyword some more values
                funcKeyw funcName4

>