我也问过自己同样的问题。为什么没有像您描述的那样用于嵌套解析器的标准组合器?我的默认模式是信任包作者,尤其是当该作者还与人合写了“Real World Haskell”时。如果缺少如此明显的功能,也许是设计使然,我应该寻找更好的方法。然而,我已经设法说服自己,这样一个方便的组合器几乎是无害的。当全有或全无类型解析器适用于内部解析时很有用。
执行
import Data.Attoparsec.Text
import qualified Data.Text as T
import Data.Text(Text)
import Control.Applicative
我已将所需的功能分成两个解析器。第一个,constP
对某些给定文本执行“就地”解析。它将常量解析器的失败替换为empty
(来自 Alternative),但没有其他副作用。
constP :: Parser a -> Text -> Parser a
constP p t = case parseOnly p t of
Left _ -> empty
Right a -> return a
第二部分来自parseOf
,它根据外部解析的结果执行恒定的内部解析。此处的empty
替代方法允许返回失败的解析而不消耗任何输入。
parseOf :: Parser Text -> Parser a -> Parser a
parseOf ptxt pa = bothParse <|> empty
where
bothParse = ptxt >>= constP pa
块引用降价可以以所需的方式编写。此实现需要完全解析生成的块。
blockQuoteMarkdown :: Parser [[Double]]
blockQuoteMarkdown = parseOf blockQuote ( markdownSurrogate <*
endOfInput
)
而不是实际的降价解析器,我只是实现了一个空格分隔双精度的快速解析器。解析器的复杂性来自于允许最后一个非空行以新行结束或不结束。
markdownSurrogate :: Parser [[Double]]
markdownSurrogate = do
lns <- many (mdLine <* endOfLine)
option lns ((lns ++) . pure <$> mdLine1)
where
mdLine = sepBy double (satisfy (==' '))
mdLine1 = sepBy1 double (satisfy (==' '))
这两个解析器负责将内部文本返回到块引号。
blockQuote :: Parser Text
blockQuote = T.unlines <$> many blockLine
blockLine :: Parser Text
blockLine = char '>' *> takeTill isEndOfLine <* endOfLine
最后,对解析器进行测试。
parseMain :: IO ()
parseMain = do
putStrLn ""
doParse "a" markdownSurrogate a
doParse "_" markdownSurrogate ""
doParse "b" markdownSurrogate b
doParse "ab" markdownSurrogate ab
doParse "a_b" markdownSurrogate a_b
doParse "badMarkdown x" markdownSurrogate x
doParse "badMarkdown axb" markdownSurrogate axb
putStrLn ""
doParse "BlockQuote ab" blockQuoteMarkdown $ toBlockQuote ab
doParse "BlockQuote a_b" blockQuoteMarkdown $ toBlockQuote a_b
doParse "BlockQuote axb" blockQuoteMarkdown $ toBlockQuote axb
where
a = "7 3 1"
b = "4 4 4"
x = "a b c"
ab = T.unlines [a,b]
a_b = T.unlines [a,"",b]
axb = T.unlines [a,x,b]
doParse desc p str = do
print $ T.concat ["Parsing ",desc,": \"",str,"\""]
let i = parse (p <* endOfInput ) str
print $ feed i ""
toBlockQuote = T.unlines
. map (T.cons '>')
. T.lines
*Main> parseMain
"Parsing a: \"7 3 1\""
Done "" [[7.0,3.0,1.0]]
"Parsing _: \"\""
Done "" []
"Parsing b: \"4 4 4\""
Done "" [[4.0,4.0,4.0]]
"Parsing ab: \"7 3 1\n4 4 4\n\""
Done "" [[7.0,3.0,1.0],[4.0,4.0,4.0]]
"Parsing a_b: \"7 3 1\n\n4 4 4\n\""
Done "" [[7.0,3.0,1.0],[],[4.0,4.0,4.0]]
"Parsing badMarkdown x: \"a b c\""
Fail "a b c" [] "endOfInput"
"Parsing badMarkdown axb: \"7 3 1\na b c\n4 4 4\n\""
Fail "a b c\n4 4 4\n" [] "endOfInput"
"Parsing BlockQuote ab: \">7 3 1\n>4 4 4\n\""
Done "" [[7.0,3.0,1.0],[4.0,4.0,4.0]]
"Parsing BlockQuote a_b: \">7 3 1\n>\n>4 4 4\n\""
Done "" [[7.0,3.0,1.0],[],[4.0,4.0,4.0]]
"Parsing BlockQuote axb: \">7 3 1\n>a b c\n>4 4 4\n\""
Fail ">7 3 1\n>a b c\n>4 4 4\n" [] "Failed reading: empty"
讨论
显着的区别在于失败的语义。比如解析axb
和blockquoted的时候axb
,分别是下面两个字符串
7 3 1
a b c
4 4 4
和
> 7 3 1
> a b c
> 4 4 4
降价解析导致
Fail "a b c\n4 4 4\n" [] "endOfInput"
而引用的结果是
Fail ">7 3 1\n>a b c\n>4 4 4\n" [] "Failed reading: empty"
降价消耗“7 3 1\n”,但这在引用的失败中没有报告。相反,失败变成全有或全无。
同样,在部分成功的情况下也不允许处理未解析的文本。但考虑到用例,我认为没有必要这样做。例如,如果一个解析看起来像下面这样
"{ <tok> unhandled }more to parse"
where{}
表示已识别的块引用上下文,并<tok>
在该内部上下文中进行解析。然后,部分成功将不得不将“未处理”从该块引用上下文中提取出来,并以某种方式将其与“更多解析”结合起来。
我认为没有一般的方法可以做到这一点,但可以通过选择内部解析器返回类型来实现。例如,通过一些解析器parseOf blockP innP :: Parser (<tok>,Maybe Text)
。但是,如果出现这种需要,我希望有一种比嵌套解析器更好的方法来处理这种情况。
也可能会担心丢失 attoparsec 部分解析。也就是说,constP
uses的实现parseOnly
将解析返回折叠Fail
并Partial
进入单个Left
失败状态。换句话说,我们失去了向内部解析器提供更多可用文本的能力。但是,请注意,要解析的文本本身就是外部解析的结果;只有在向外部解析提供了足够的文本后,它才可用。所以这也不应该是一个问题。