旧的非工作示例的旧答案
您安装了哪个版本的parsec? 3.1.9 为我做这个:
Prelude> :m + Text.Parsec Text.Parsec.String
Prelude Text.Parsec Text.Parsec.String> :set prompt Main>
Main> let parser = choice (map (try . string) ["foo", "fob", "bar"]) :: GenParser Char st String
Main> runParser parser () "Hey" "fo "
Left "Hey" (line 1, column 1):
unexpected " "
expecting "foo", "fob" or "bar"
Main> runParser parser () "Hey" "fo"
Left "Hey" (line 1, column 1):
unexpected end of input
expecting "foo", "fob" or "bar"
添加的<?> error_message 没有任何改变,只是将最后一行更改为expecting expected one of ['foo', 'fob', 'bar']。
如何从 Parsec 中提取更多错误
因此,在这种情况下,您不应相信错误消息会详尽地说明系统中可用的信息。让我为Text.Parsec.Error:Message 提供一个时髦的Show 实例(基本上就是deriving (Show) 的情况),这样你就可以看到Parsec 的结果:
Main> :m + Text.Parsec.Error
Main> instance Show Message where show m = (["SysUnExpect", "UnExpect", "Expect", "Message"] !! fromEnum m) ++ ' ' : show (messageString m)
Main> case runParser parser () "" "ta" of Left pe -> errorMessages pe
[SysUnExpect "\"t\"",SysUnExpect "",SysUnExpect "",Expect "\"head\"",Expect "\"tail\"",Expect "\"tales\""]
您可以看到秘密 choice 正在将其所有信息转储到一堆并行消息中,并将“意外的文件结尾”存储为SysUnExpect ""。 ParseError 的 show 实例显然获取了第一个 SysUnExpect 但所有 Expect 消息并转储它们供您查看。
目前执行此操作的确切函数是Text.Parsec.Error:showErrorMessages。错误消息应该是有序的,并根据构造函数分为 4 个块; SysUnExpect 块是通过一个特殊的显示函数发送的,如果有真正的 UnExpect 元素,它会完全隐藏文本,否则只显示第一个 SysUnExpect 消息:
showSysUnExpect | not (null unExpect) ||
null sysUnExpect = ""
| null firstMsg = msgUnExpected ++ " " ++ msgEndOfInput
| otherwise = msgUnExpected ++ " " ++ firstMsg
可能值得重写或向上游发送错误,因为这是一种奇怪的行为,并且数据结构不太适合它们。首先,简而言之,您的问题是:似乎每个Message 都应该有一个SourcePos,而不是每个 ParseError。
所以,有一个更早的步骤,mergeErrors,它更喜欢 ParseErrors 和后来的SourcePos-es。这不会触发,因为消息没有SourcePos,这意味着来自choice 的所有错误都从字符串的开头开始,而不是从匹配的最大点开始。例如,您可以在解析 "tai" 时不会卡住这一点:
let parser = try (string "head") <|> choice (map (try . (string "ta" >>) . string) ["il", "les"]) :: GenParser Char st Strinh
其次,除此之外,也许我们应该将一起发送的消息绑定在一起(所以默认消息是unexpected 't', expected "heads" | unexpected end-of-file, expected 'tails' | unexpected end-of-file, expected 'tales',除非你用<?> 覆盖它)。第三,可能 ParseError 构造函数应该被导出;第四,Message 中的枚举类型真的很难看,最好放在ParseError {systemUnexpected :: [Message], userUnexpected :: [Message], expected :: [Message], other :: [Message]} 或其他东西中,即使是现在的化身。 (例如,如果消息没有按特定顺序排列,ParseError 的当前 Show 将巧妙地中断。)
与此同时,我建议您为 ParseError 编写自己的 show 变体。