如何在attoparsec中结合sepBy与终止字符实现解析?
使用Attoparsec解析带参数的消息格式
需求说明
我需要解析类似这样的文本行:
ItemCount count@Int: You have purchased #{showInt count} #{plural count "item" "items"}
解析后要得到如下的数据结构:
Message { key = "ItemCount", value = "You have purchased #{showInt count} #{plural count \"item\" \"items\"}", parameters = [Parameter {name = "count", paramType = "Int"}] }
行首的格式支持三种情况:
- 无参数:
ItemCount: - 单个参数:
ItemCount count@Int: - 多个参数:
ItemCount count@Int count2@Int:
我现在的核心困扰是:怎么实现**“直到遇到:为止的sepBy”**功能——也就是参数列表要解析到冒号就停止。虽然可以先把冒号前的内容截取出来再内部解析,但错误处理会很麻烦,想找个更优雅的方案。
我当前的代码
import Control.Applicative import Data.Attoparsec.Text import Data.Text (Text) data Parameter = Parameter { name :: Text, paramType :: Text } deriving (Eq, Show) data Message = Message { key :: Text, value :: Text, parameters :: [Parameter] } deriving (Eq, Show) data KeyAndParameters = KeyAndParameters Text [Parameter] parseParameter :: Parser Parameter parseParameter = do n <- takeWhile1 (notInClass "@") _ <- char '@' t <- takeWhile1 (const True) return $ Parameter { name = n, paramType = t } parseKeyAndParameters :: Parser KeyAndParameters parseKeyAndParameters = do keyV <- takeWhile1 (notInClass " :") params <- parseParameter `sepBy` (char ' ') return $ KeyAndParameters keyV params
解决方案
要解决这个问题,我们可以对代码做几个关键调整,让解析逻辑更精准且符合需求:
1. 修正参数解析的边界
首先,原来的parseParameter里,takeWhile1 (const True)会一直读到输入结束,这显然不对——参数的名称和类型都不应该包含空格或冒号。我们先修正这个解析器:
parseParameter :: Parser Parameter parseParameter = do n <- takeWhile1 (notInClass "@ :") -- 名称不能包含@、空格、冒号 _ <- char '@' t <- takeWhile1 (notInClass " :") -- 类型不能包含空格、冒号 return $ Parameter n t
2. 实现“到冒号为止的参数列表”
我们可以利用lookAhead来预判下一个字符是不是冒号,从而终止参数的解析。调整parseKeyAndParameters如下:
parseKeyAndParameters :: Parser KeyAndParameters parseKeyAndParameters = do keyV <- takeWhile1 (notInClass " :") -- 解析参数,直到下一个字符是冒号 params <- parseParameter `sepBy` char ' ' <* lookAhead (char ':') _ <- char ':' -- 消耗掉冒号 return $ KeyAndParameters keyV params
这里的sepBy会自动处理0个或多个参数的情况:如果没有参数,sepBy返回空列表,完全符合ItemCount:这种场景。
3. 完整的消息解析器
最后,把前面的解析器和消息内容的解析结合起来,完成整个Message的解析:
parseMessage :: Parser Message parseMessage = do KeyAndParameters k ps <- parseKeyAndParameters _ <- char ' ' -- 跳过冒号后的空格 val <- takeText -- 读取剩余所有文本作为消息内容 return $ Message k val ps
测试效果
比如解析输入ItemCount count@Int count2@String: Hello #{user}, you have #{count} items,会得到:
Message { key = "ItemCount", value = "Hello #{user}, you have #{count} items", parameters = [ Parameter {name = "count", paramType = "Int"}, Parameter {name = "count2", paramType = "String"} ] }
这种方式不需要手动截取字符串,错误处理完全由Attoparsec自动处理,逻辑更清晰也更健壮。
内容的提问来源于stack exchange,提问作者Chris Stryczynski
相关产品推荐
相关产品推荐

