parsec:解析嵌套的代码块

时间:2015-12-17 19:39:59

标签: 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

缩进由标签完成。每个块由keyword启动,并在同一行中添加一些其他值。缩进的所有内容都属于同一个块。 所有函数调用(以funcKeyw关键字开头)后,可能会有子keyword块(由“空”行分隔;“空”表示没有任何内容它或空白字符)。

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解析器在子块启动时不会停止解析,但会返回错误unexpected 'keyword'

我该如何避免?我想我可以使用trychoice为每一行选择正确的解析器,但我想要求函数调用在子块之前。

1 个答案:

答案 0 :(得分:2)

我注意到的一件事是sepBy组合器有一些意想不到的行为,即如果分隔符开始被解析,那么失败,整个sepBy失败,而不是简单返回到目前为止解析的内容。您可以使用以下变体,这些变体因try内的其他sepBy1Try而异:

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
                         }

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

最后,更改以下内容:

try

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

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

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

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"
  ]