【问题标题】:Haskell ReadP parse at least one of the listHaskell ReadP 解析列表中的至少一个
【发布时间】:2019-12-29 13:31:09
【问题描述】:

我尝试使用 ReadP 标准库解析命令(创建)。我的命令应该以字符串create 开头,然后至少包含一个单词/标签/到期,并可能包含一个选项。这是我的实际表达:

createExpr :: ReadP [Arg]
createExpr = do
  skipSpaces
  cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
  skipSpaces
  rest <-
    many1
    $   (AddWord <$> wordExpr)
    <|> (AddTag <$> addTagExpr)
    <|> (SetDue <$> dueExpr)
    <|> (AddOpt <$> optExpr)
  skipSpaces
  return $ cmd : rest

问题是,如果我只用一个选项调用create,它就可以很好地解析。但它不应该,因为我希望至少有一个词/标签/到期。我该如何表达?


[编辑] 感谢@M. Aroosi,我找到了解决方案。

事实上,我用错了运算符。 &lt;++本地、独家、偏左的选择,更符合我的需求。一旦一个表达式被匹配,它不应该检查其他的:

notAnOpt arg = case arg of
  AddOpt _ -> False
  _        -> True


createExpr :: ReadP [Arg]
createExpr = do
  skipSpaces
  cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
  skipSpaces
  rest <-
    many1
    $   (AddTag <$> addTagExpr)
    <++ (SetDue <$> dueExpr)
    <++ (AddOpt <$> optExpr)
    <++ (AddWord <$> wordExpr)
  skipSpaces
  guard $ isJust $ find notAnOpt rest
  return $ cmd : rest

【问题讨论】:

  • 您可以检查 rest 是否至少有 1 个单词/标签/到期(例如,filter 然后 null 检查)并使用 Control.Monad 中的 guard 来确保在这种情况下解析器会失败。
  • 我不确定完全理解你所说的。你能给我举个例子吗?
  • 不要添加问题的答案...
  • 你读过所有的cmets吗?多亏了我收到的帮助,我找到了解决方案。我要求所有者(M. Aroosi)用我发现的内容更新他的答案,然后我会验证它。

标签: parsing haskell parsec parser-combinators


【解决方案1】:

一个简单的解决方案是使用来自Control.Monadguard
假设像isOpt :: Arg -&gt; Bool 这样的函数类似于

isOpt :: Arg -> Bool
isOpt (AddOpt _) = True
isOpt _          = False 

然后您对createExpr 的定义更改为

createExpr :: ReadP [Arg]
createExpr = do
    skipSpaces
    cmd <- SetCmd <$> cmdAliasExpr ["create", "add"]
    skipSpaces
    rest <-
      many1
      $   (AddWord <$> wordExpr)
      <|> (AddTag <$> addTagExpr)
      <|> (SetDue <$> dueExpr)
      <|> (AddOpt <$> optExpr)
    guard $ at_least_one_non_optional rest
    skipSpaces
    return $ cmd : rest
  where at_least_one_non_optional = not . null . filter (not . isOpt)

guard 在其参数为 False 时基本上会使解析器失败,更一般地说,当参数为 False 时,它通过返回 empty 与任何 Alternative 一起工作。

【讨论】:

  • 我明白你的意思。我用isJust $ find atLeastOne rest 也达到了同样的效果。但这会导致另一个问题。如果我输入create --opt,我就有[([SetCmd "create",AddWord "--json"],"")]。我猜是因为optExpr 失败,它会退回到wordExpr
  • @soywod 这听起来像你的wordExproptExpr 都接受"--json",在这种情况下,解析器(AddWord &lt;$&gt; wordExpr) &lt;|&gt; (AddTag &lt;$&gt; addTagExpr) &lt;|&gt; (SetDue &lt;$&gt; dueExpr) &lt;|&gt; (AddOpt &lt;$&gt; optExpr) 会尝试wordExproptExpr 两者都成功。
  • 两者都尝试是什么意思?只有在第一次失败时它才会尝试一次,不是吗?事实上,wordExpr 就像“垃圾”。与 opt/tag/due 不匹配的所有内容都将作为一个单词结束。
  • (&lt;|&gt;) 被定义为(+++) for ReadP 这是可交换的,它尝试所有选项并返回所有结果。例如,尝试使用解析器(0 &lt;$ string "hello") &lt;|&gt; (1 &lt;$ string "hello") 查看解析“hello”的结果。您可以使用(&lt;++) 进行左偏选择,即如果左解析器成功,它将不会尝试右解析器。
  • 是的,(&lt;++) 似乎更符合我的需求。我会和你玩一会儿然后回来找你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-12
  • 1970-01-01
  • 1970-01-01
  • 2022-01-13
  • 1970-01-01
相关资源
最近更新 更多