问题
我正在为自学滚动一个不确定的解析器,它看起来像:
newtype Parser a b = P { runP :: [a] -> [(b, [a])] }
而且我希望能够放入一个可能 是形式的子例程:[a] -> [b]
,它需要一个字符缓冲区并将其发送到结果列表。这里的诀窍是子例程本身是一个有状态的计算,它在每次成功调用时转换状态(将其视为有限状态机)。具体来说:
- 如果子例程输出空 list
[]
,则解析器将再插入一个 char 到缓冲区中并将其交给子例程,该子例程再次运行。 - 如果子例程输出非空列表
[b]
,则首先刷新缓冲区,然后解析器再将一个字符插入缓冲区,将其交给子例程。[b]
存储在某处 - 在达到逃生条件之前,步骤 1 和 2 会一遍又一遍地运行。所有中间结果都应该以某种方式组合起来。
一旦达到转义条件,子例程将结果
bs
返回给解析器,并将其与剩余的流结合起来,as
如下所示:rs = fmap (flip (,) as) bs :: [(b,[a])]
从而满足签名runP
该函数可能具有以下签名:withf :: ([a] -> [b]) -> Parser a b
重要的是它withf g
必须是一个解析器,所以我可以使用<*>
. 注意函数签名Suggestg
是一个纯函数,所以它不太可能是正确的。
尝试过的解决方案
我尝试使用各种协程包来实现这一点,但对我来说,lift
将解析器放入协程计算上下文中更有意义,将它与也提升到上下文中的转换器组合在一起,这意味着它不再是解析器。
我还尝试将其实现withf
为可以访问 Parser 值构造函数的原始函数。基本上将步骤 1..4 转换为代码。我这里最大的问题是谁负责什么信息:
- 缓冲区状态
- 中间结果的状态
- 如何组合中间结果的逻辑。
- 应该如何实现转义条件,或者更好地组合成
withf
我还尝试了直接在解析器中烘焙的各种自制协程实现(因此不使用Parser
上面定义的类型),但收效甚微。
任何能指出我正确方向的人都非常感谢。