haskell - parsec:解析嵌套代码块

标签 haskell parsec parser-combinators

我想解析以下文本:

keyword some more values
        funcKeyw funcName1
        funcKeyw funcName2

        funcKeyw funcName3

        keyword some more values
                 funcKeyw funcName2

        keyword some more values
                 funcKeyw funcName4

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

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解析器在子 block 启动时并没有停止解析,而是返回错误unexpected 'keyword'

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

最佳答案

我注意到的一件事是 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允许它回溯到“下一个级别”。

最后,更改以下内容:

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

与此处的 sepBy 的推理相同...


为清楚起见,使用简单 pretty-print 进行测试:

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

>

关于haskell - parsec:解析嵌套代码块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34342911/

相关文章:

haskell - 具有两个参数的构造函数的应用样式解析器

parsing - 创建类型为 Parser a -> Parser b -> Parser (要么 a b)的解析器组合器

haskell - 秒差距行开始模式?

python - 与 Python 相比,在 Haskell 中调用 c 函数

Haskell cabal+hsc2hs

haskell - 在 Haskell 中以应用风格组合验证器

c++ - GLL Parser Combinator or Generator in/for C or C++

haskell - HasResolution 类型类

scala - 模式匹配时如何摆脱 "unchecked due to erasure"警告

Scala解析器组合器: how to invert matches?