haskell Parsec中“sepBy”的分隔符分析器的急切性

nszi6y05  于 2022-12-19  发布在  其他
关注(0)|答案(2)|浏览(173)

这是一个与Haskell的Parsec中解析器优先级的更一般性问题有关的问题,所以请随意以更一般性的方式回答我的问题。
假设我有一个p :: Parsec String () Int,它消耗了String中的每个字符,并在最后对它的ASCII值求和。我想把这个p变成另一个相同类型的解析器,它计算由换行符分隔的子字符串的ASCII求和结果,并取它们的乘积。例如,AB\nZ\nCEF解析为(65+66)*(90)*(67+69+70)=2428740
我可以在输入上使用lines,将元素Map到它们的解析值,并获取它们的product,但是我希望保持这个Parsec-native;至少我需要一个通用函数。
我可以使用sepBy p newline,但是只有当我的p被专门设计为拒绝解析\n时,这才能起作用,因为这在组合上不是很有头脑。

cgh8pdjw

cgh8pdjw1#

Parsec并不显式地管理其解析器的优先级,而是使用各种控制结构组合符(如序列或循环)来组合解析器,因此序列p >> q意味着“首先运行解析器p,一旦成功完成,运行解析器q;如果其中一个失败,则整个操作失败”。对输入流的控制来自自下而上。解析器决定何时完成,成功还是失败,以及它消耗了多少输入流,父组合子对解析器的控制基本上限于全有或全无的决定:要么接受解析器所做的,要么完全拒绝它。
这意味着一个特定的解析器,比如p,它会吞掉流的其余部分,不能由父组合子控制,比如sepBysepBy所能做的就是运行它直到完成,然后在再次运行p之前检查分隔符解析器是否成功。
编写Parsec解析器的正确方法是修改p,使其拒绝换行符,这可能不会让您觉得它是“有组合意识的”,但这就是Parsec的工作方式,当您编写更实际的Parsec解析器时,您将发现Parsec解析器的组合方面依赖于各个解析器很好地处理输入流的“共享资源”。必须 * 做出正确的本地决策,决定要解析多少输入流。您还会发现,编写更高级别的组合子时,经常需要了解其复合解析器的非本地知识,以避免陷入“try地狱”。我在这里特别考虑的是<|>替代品链中解析器的排序。
这只是Parsec设计的一个基本局限,而且它被看作是“组合性”和“实用性/效率”之间的必要权衡。
话虽如此,不要忘记你是用函数式编程语言编程的,解析器只是值,函数可以创建它们。如果你想要一个可重用的p版本,尝试一下:

type Parser = Parsec String ()

sumWhile :: (Char -> Bool) -> Parser Int
sumWhile p = sum . map ord <$> many (satisfy p)

p = sumWhile (/= '\n')

这个例子不是很好,因为老实说,这个组件不值得重用,但是这种技术对于更复杂的可重用组件是有帮助的。

0g0grzrc

0g0grzrc2#

一般来说,组成整个Parsec解析器的子解析器需要相互协作。如果一个解析器想要使用所有可用的输入,那么另一个解析器就无法将其“沙箱化”到一个更大的解析器的上下文中。如果您希望某个解析器在下一个换行符之前使用所有输入,那么您必须教它不要使用换行符。
注意,我用“在单个较大解析器的上下文中”来限定它,这是因为,正如您所建议的,您可以通过在Parsec级别之上做一些事情来解决这个问题:例如,首先对输入调用lines,然后分别解析每一行。在输入到解析器之前进行预处理是一个古老的传统。2例如,参见词法分析器。
你提到你至少想要一个“通用函数Parsec String u a -> Parsec [String] u [a]“,我不确定这对你是否有用,因为你不能把得到的Parsec [String] u [a]和你为剩下的任务编写的其他Parsec String u a子解析器结合起来。

相关问题