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'
。
我怎样才能避免这种情况?我想我可以使用 try
或 choice
为每一行选择正确的解析器,但我想要求函数调用在子块之前进行。
我注意到的一件事是 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
>
我想解析以下文本:
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'
。
我怎样才能避免这种情况?我想我可以使用 try
或 choice
为每一行选择正确的解析器,但我想要求函数调用在子块之前进行。
我注意到的一件事是 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
>