为了更好地了解packrat,我尝试查看the provided implementation附带的the paper(我专注于bind
):
instance Derivs d => Monad (Parser d) where
-- Sequencing combinator
(Parser p1) >>= f = Parser parse
where parse dvs = first (p1 dvs)
first (Parsed val rem err) =
let Parser p2 = f val
in second err (p2 rem)
first (NoParse err) = NoParse err
second err1 (Parsed val rem err) =
Parsed val rem (joinErrors err1 err)
second err1 (NoParse err) =
NoParse (joinErrors err1 err)
-- Result-producing combinator
return x = Parser (\dvs -> Parsed x dvs (nullError dvs))
-- Failure combinator
fail [] = Parser (\dvs -> NoParse (nullError dvs))
fail msg = Parser (\dvs -> NoParse (msgError (dvPos dvs) msg))
对我来说,解析器组合器(例如this simplified version of Parsec)看起来(错误处理除外):
bind :: Parser a -> (a -> Parser b) -> Parser b
bind p f = Parser $ \s -> concatMap (\(a, s') -> parse (f a) s') $ parse p s
我很困惑,因为在那之前我以为最大的区别是packrat是带有备注部分的解析器生成器。 令人遗憾的是,此实现似乎未使用此概念。
解析器组合器和packrat在实现级别上有什么大区别?
PS:我也看过Papillon,但这似乎与本文随附的实现有很大不同。
答案 0 :(得分:2)
这里的要点实际上是,这个Packrat解析器组合器库不是Packrat算法的完整实现,而是更像是一组可以在不同packrat解析器之间重用的定义。
packrat算法的真正窍门(即解析结果的记忆)发生在其他地方。 看下面的代码(摘自福特的论文):
data Derivs = Derivs {
dvAdditive :: Result Int,
dvMultitive :: Result Int,
dvPrimary :: Result Int,
dvDecimal :: Result Int,
dvChar :: Result Char}
pExpression :: Derivs -> Result ArithDerivs Int
Parser pExpression = (do char ’(’
l <- Parser dvExpression
char ’+’
r <- Parser dvExpression
char ’)’
return (l + r))
</> (do Parser dvDecimal)
在这里,重要的是要注意,通过简单地投影Derivs结构的适当组件,表达式解析器对其自身的递归调用就被破坏了(以一种开放递归的方式)。
然后将这种递归结点绑定到“递归关联函数”(同样取自福特的论文):
parse :: String -> Derivs
parse s = d where
d = Derivs add mult prim dec chr
add = pAdditive d
mult = pMultitive d
prim = pPrimary d
dec = pDecimal d
chr = case s of
(c:s’) -> Parsed c (parse s’)
[] -> NoParse
这些片段确实是packrat技巧发生的地方。 重要的是要理解,这个技巧不能在传统的解析器组合器库中以标准方式实现(至少在像Haskell这样的纯编程语言中),因为它需要知道语法的递归结构。 有一些解析器组合器库的实验方法,它们使用语法的递归结构的特定表示形式,并且有可能提供Packrat的标准实现。 例如,我自己的grammar-combinators库(不维护atm,抱歉)提供了Packrat的实现。
答案 1 :(得分:0)
如其他地方所述,packrat不能替代组合器,而是在那些解析器中的实现选项。 Pyparsing是一个组合器,它通过调用ParserElement.enablePackrat()
提供可选的packrat优化。它的实现几乎是使用_parse
方法直接替代pyparsing的_parseNoCache
方法(重命名为_parseCache
)的方法。
Pyparsing使用固定长度的FIFO队列作为其缓存,因为一旦组合器完全匹配缓存的表达式并继续通过输入流,packrat缓存条目就会过时。可以将自定义缓存大小作为整数参数传递给enablePackrat()
,如果传递了None,则表示缓存不受限制。对于提供的Verilog解析器,默认值为128的packrat缓存的效率仅比无边界缓存低2%,大大节省了内存。