【问题标题】:parsec: parse nested code blocksparsec:解析嵌套代码块
【发布时间】:2015-12-17 19:39:58
【问题描述】:

我想解析以下文本:

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 为每一行选择正确的解析器,但我想要求函数调用在子块之前。

【问题讨论】:

    标签: haskell parsec parser-combinators


    【解决方案1】:

    我注意到的一件事是 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 相同...


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

    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
    
    >
    

    【讨论】:

    • 很好的答案,非常感谢!我用liftM2 (:) p (many (try $ sep *&gt; p)) 替换了sepBy1Try 的实现,以使do 消失。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多