You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:17:02