我想解析以下文本:
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'
。
我怎样才能避免这种情况?我想我可以使用 try
或 choice
为每行选择正确的解析器,但我想要求函数调用位于子 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/