解析器组合器和packrat算法之间的实现差异

时间:2018-11-21 20:02:30

标签: parsing haskell parsec parser-combinators peg

为了更好地了解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,但这似乎与本文随附的实现有很大不同。

2 个答案:

答案 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%,大大节省了内存。